diff --git a/packages/Osu/Android.mk b/packages/Osu/Android.mk
index 15744c56fd21f..0c9006a4abbd2 100644
--- a/packages/Osu/Android.mk
+++ b/packages/Osu/Android.mk
@@ -6,6 +6,9 @@ LOCAL_RESOURCE_DIR := $(LOCAL_PATH)/res
LOCAL_MODULE_TAGS := optional
LOCAL_SRC_FILES := $(call all-java-files-under, src)
+LOCAL_SRC_FILES += \
+ src/com/android/hotspot2/app/IOSUAccessor.aidl \
+ src/com/android/hotspot2/flow/IFlowService.aidl
LOCAL_JAVA_LIBRARIES := telephony-common ims-common bouncycastle conscrypt
diff --git a/packages/Osu/AndroidManifest.xml b/packages/Osu/AndroidManifest.xml
index 288f1a488c66b..fa9a656e6b95f 100644
--- a/packages/Osu/AndroidManifest.xml
+++ b/packages/Osu/AndroidManifest.xml
@@ -1,6 +1,23 @@
+
+ package="com.android.hotspot2">
@@ -12,13 +29,12 @@
-
+
@@ -28,30 +44,12 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
diff --git a/packages/Osu/res/layout/osu_web_view.xml b/packages/Osu/res/layout/osu_web_view.xml
new file mode 100644
index 0000000000000..4eafb39d17135
--- /dev/null
+++ b/packages/Osu/res/layout/osu_web_view.xml
@@ -0,0 +1,13 @@
+
+
+
+
diff --git a/packages/Osu/src/com/android/MainActivity.java b/packages/Osu/src/com/android/MainActivity.java
deleted file mode 100644
index 763a988146b73..0000000000000
--- a/packages/Osu/src/com/android/MainActivity.java
+++ /dev/null
@@ -1,442 +0,0 @@
-package com.android;
-
-import android.app.Activity;
-import android.app.AlertDialog;
-import android.app.IntentService;
-import android.app.Notification;
-import android.app.NotificationManager;
-import android.app.PendingIntent;
-import android.app.TaskStackBuilder;
-import android.content.BroadcastReceiver;
-import android.content.Context;
-import android.content.DialogInterface;
-import android.content.Intent;
-import android.graphics.BitmapFactory;
-import android.graphics.drawable.BitmapDrawable;
-import android.net.wifi.WifiConfiguration;
-import android.net.wifi.WifiInfo;
-import android.net.wifi.WifiManager;
-import android.os.Binder;
-import android.os.Bundle;
-import android.os.IBinder;
-import android.util.Log;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.ViewGroup;
-import android.widget.AdapterView;
-import android.widget.ArrayAdapter;
-import android.widget.ImageView;
-import android.widget.ListView;
-import android.widget.TextView;
-
-import com.android.anqp.OSUProvider;
-import com.android.hotspot2.AppBridge;
-import com.android.hotspot2.PasspointMatch;
-import com.android.hotspot2.osu.OSUInfo;
-import com.android.hotspot2.osu.OSUManager;
-
-import org.xml.sax.SAXException;
-
-import java.io.IOException;
-import java.util.Collections;
-import java.util.List;
-import java.util.Locale;
-import java.util.concurrent.TimeUnit;
-
-//import com.android.Osu.R;
-
-/**
- * Main activity.
- */
-public class MainActivity extends Activity {
- private static final int NOTIFICATION_ID = 0; // Used for OSU count
- private static final int NOTIFICATION_MESSAGE_ID = 1; // Used for other messages
- private static final Locale LOCALE = java.util.Locale.getDefault();
-
- private static volatile OSUService sOsuService;
-
- private ListView osuListView;
- private OsuListAdapter2 osuListAdapter;
- private String message;
-
- public MainActivity() {
-
- }
-
- @Override
- protected void onResume() {
- super.onResume();
- if (message != null) {
- showDialog(message);
- message = null;
- }
- }
-
- @Override
- public void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
-
- Intent intent = getIntent();
- Bundle bundle = intent.getExtras();
-
- if (bundle == null) { // User interaction
- if (sOsuService == null) {
- Intent serviceIntent = new Intent(this, OSUService.class);
- serviceIntent.putExtra(ACTION_KEY, "dummy-key");
- startService(serviceIntent);
- return;
- }
-
- List osuInfos = sOsuService.getOsuInfos();
-
- setContentView(R.layout.activity_main);
- Log.d("osu", "osu count:" + osuInfos.size());
- View noOsuView = findViewById(R.id.no_osu);
- if (osuInfos.size() > 0) {
- noOsuView.setVisibility(View.GONE);
- osuListAdapter = new OsuListAdapter2(this, osuInfos);
- osuListView = (ListView) findViewById(R.id.profile_list);
- osuListView.setAdapter(osuListAdapter);
- osuListView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
- @Override
- public void onItemClick(AdapterView> adapterView, View view, int position, long id) {
- OSUInfo osuData = (OSUInfo) adapterView.getAdapter().getItem(position);
- Log.d("osu", "launch osu:" + osuData.getName(LOCALE)
- + " id:" + osuData.getOsuID());
- sOsuService.selectOsu(osuData.getOsuID());
- finish();
- }
- });
- } else {
- noOsuView.setVisibility(View.VISIBLE);
- }
- } else if (intent.getAction().equals(AppBridge.ACTION_OSU_NOTIFICATION)) {
- if (bundle.containsKey(AppBridge.OSU_COUNT)) {
- showOsuCount(bundle.getInt("osu-count", 0), Collections.emptyList());
- } else if (bundle.containsKey(AppBridge.PROV_SUCCESS)) {
- showStatus(bundle.getBoolean(AppBridge.PROV_SUCCESS),
- bundle.getString(AppBridge.SP_NAME),
- bundle.getString(AppBridge.PROV_MESSAGE),
- null);
- } else if (bundle.containsKey(AppBridge.DEAUTH)) {
- showDeauth(bundle.getString(AppBridge.SP_NAME),
- bundle.getBoolean(AppBridge.DEAUTH),
- bundle.getInt(AppBridge.DEAUTH_DELAY),
- bundle.getString(AppBridge.DEAUTH_URL));
- }
- /*
- else if (bundle.containsKey(AppBridge.OSU_INFO)) {
- List osus = printOsuDataList(bundle.getParcelableArray(AppBridge.OSU_INFO));
- showOsuList(osus);
- }
- */
- }
- }
-
- private void showOsuCount(int osuCount, List osus) {
- if (osuCount > 0) {
- printOsuDataList(osus);
- sendNotification(osuCount);
- } else {
- cancelNotification();
- }
- finish();
- }
-
- private void showStatus(boolean provSuccess, String spName, String provMessage,
- String remoteStatus) {
- if (provSuccess) {
- sendDialogMessage(
- String.format("Credentials for %s was successfully installed", spName));
- } else {
- if (spName != null) {
- if (remoteStatus != null) {
- sendDialogMessage(
- String.format("Failed to install credentials for %s: %s: %s",
- spName, provMessage, remoteStatus));
- } else {
- sendDialogMessage(
- String.format("Failed to install credentials for %s: %s",
- spName, provMessage));
- }
- } else {
- sendDialogMessage(
- String.format("Failed to contact OSU: %s", provMessage));
- }
- }
- }
-
- private void showDeauth(String spName, boolean ess, int delay, String url) {
- String delayReadable = getReadableTimeInSeconds(delay);
- if (ess) {
- if (delay > 60) {
- sendDialogMessage(
- String.format("There is an issue connecting to %s [for the next %s]. " +
- "Please visit %s for details", spName, delayReadable, url));
- } else {
- sendDialogMessage(
- String.format("There is an issue connecting to %s. " +
- "Please visit %s for details", spName, url));
- }
- } else {
- sendDialogMessage(
- String.format("There is an issue with the closest Access Point for %s. " +
- "You may wait %s or move to another Access Point to " +
- "regain access. Please visit %s for details.",
- spName, delayReadable, url));
- }
- }
-
- private static final String ACTION_KEY = "action";
-
- public static class WifiReceiver extends BroadcastReceiver {
- @Override
- public void onReceive(Context c, Intent intent) {
- Log.d(OSUManager.TAG, "OSU App got intent: " + intent.getAction());
- Intent serviceIntent;
- serviceIntent = new Intent(c, OSUService.class);
- serviceIntent.putExtra(ACTION_KEY, intent.getAction());
- serviceIntent.putExtras(intent);
- c.startService(serviceIntent);
- }
- }
-
- public static class OSUService extends IntentService {
- private OSUManager mOsuManager;
- private final IBinder mBinder = new Binder();
-
- public OSUService() {
- super("OSUService");
- }
-
- @Override
- public int onStartCommand(Intent intent, int flags, int startId) {
- onHandleIntent(intent);
- return START_STICKY;
- }
-
- @Override
- public void onCreate() {
- super.onCreate();
- Log.d("YYY", String.format("Service %x running, OSU %x",
- System.identityHashCode(this), System.identityHashCode(mOsuManager)));
- if (mOsuManager == null) {
- mOsuManager = new OSUManager(this);
- }
- sOsuService = this;
- }
-
- @Override
- public void onDestroy() {
- super.onDestroy();
- Log.d("YYY", String.format("Service %x killed", System.identityHashCode(this)));
- }
-
- @Override
- public IBinder onBind(Intent intent) {
- return mBinder;
- }
-
- @Override
- protected void onHandleIntent(Intent intent) {
- if (intent == null) {
- Log.d(OSUManager.TAG, "Null intent!");
- return;
- }
- Bundle bundle = intent.getExtras();
- WifiManager wifiManager = (WifiManager) getSystemService(Context.WIFI_SERVICE);
- Log.d(OSUManager.TAG, "OSU Service got intent: " + intent.getStringExtra(ACTION_KEY));
- switch (intent.getStringExtra(ACTION_KEY)) {
- case WifiManager.SCAN_RESULTS_AVAILABLE_ACTION:
- mOsuManager.pushScanResults(wifiManager.getScanResults());
- break;
- case WifiManager.PASSPOINT_WNM_FRAME_RECEIVED_ACTION:
- long bssid = bundle.getLong(WifiManager.EXTRA_PASSPOINT_WNM_BSSID);
- String url = bundle.getString(WifiManager.EXTRA_PASSPOINT_WNM_URL);
-
- try {
- if (bundle.containsKey(WifiManager.EXTRA_PASSPOINT_WNM_METHOD)) {
- int method = bundle.getInt(WifiManager.EXTRA_PASSPOINT_WNM_METHOD);
- if (method != OSUProvider.OSUMethod.SoapXml.ordinal()) {
- Log.w(OSUManager.TAG, "Unsupported remediation method: " + method);
- }
- PasspointMatch match = null;
- if (bundle.containsKey(WifiManager.EXTRA_PASSPOINT_WNM_PPOINT_MATCH)) {
- int ordinal =
- bundle.getInt(WifiManager.EXTRA_PASSPOINT_WNM_PPOINT_MATCH);
- if (ordinal >= 0 && ordinal < PasspointMatch.values().length) {
- match = PasspointMatch.values()[ordinal];
- }
- }
- mOsuManager.wnmRemediate(bssid, url, match);
- } else if (bundle.containsKey(WifiManager.EXTRA_PASSPOINT_WNM_ESS)) {
- boolean ess = bundle.getBoolean(WifiManager.EXTRA_PASSPOINT_WNM_ESS);
- int delay = bundle.getInt(WifiManager.EXTRA_PASSPOINT_WNM_DELAY);
- mOsuManager.deauth(bssid, ess, delay, url);
- } else {
- Log.w(OSUManager.TAG, "Unknown WNM event");
- }
- } catch (IOException | SAXException e) {
- Log.w(OSUManager.TAG, "Remediation event failed to parse: " + e);
- }
- break;
- case WifiManager.PASSPOINT_ICON_RECEIVED_ACTION:
- mOsuManager.notifyIconReceived(
- bundle.getLong(WifiManager.EXTRA_PASSPOINT_ICON_BSSID),
- bundle.getString(WifiManager.EXTRA_PASSPOINT_ICON_FILE),
- bundle.getByteArray(WifiManager.EXTRA_PASSPOINT_ICON_DATA));
- break;
- case WifiManager.CONFIGURED_NETWORKS_CHANGED_ACTION:
- boolean multiNetwork =
- bundle.getBoolean(WifiManager.EXTRA_MULTIPLE_NETWORKS_CHANGED, false);
- if (multiNetwork) {
- mOsuManager.networkChanged(null);
- } else {
- WifiConfiguration configuration =
- intent.getParcelableExtra(WifiManager.EXTRA_WIFI_CONFIGURATION);
- switch (bundle.getInt(WifiManager.EXTRA_CHANGE_REASON,
- WifiManager.CHANGE_REASON_CONFIG_CHANGE)) {
- case WifiManager.CHANGE_REASON_ADDED:
- break;
- case WifiManager.CHANGE_REASON_REMOVED:
- mOsuManager.networkDeleted(configuration);
- break;
- case WifiManager.CHANGE_REASON_CONFIG_CHANGE:
- mOsuManager.networkChanged(configuration);
- break;
- }
- }
- mOsuManager.networkChanged((WifiConfiguration)
- intent.getParcelableExtra(WifiManager.EXTRA_WIFI_CONFIGURATION));
- break;
- case WifiManager.WIFI_STATE_CHANGED_ACTION:
- int state = bundle.getInt(WifiManager.EXTRA_WIFI_STATE);
- if (state == WifiManager.WIFI_STATE_DISABLED) {
- mOsuManager.wifiStateChange(false);
- } else if (state == WifiManager.WIFI_STATE_ENABLED) {
- mOsuManager.wifiStateChange(true);
- }
- break;
- case WifiManager.NETWORK_STATE_CHANGED_ACTION:
- mOsuManager.networkConnectEvent((WifiInfo)
- intent.getParcelableExtra(WifiManager.EXTRA_WIFI_INFO));
- break;
- }
- }
-
- public List getOsuInfos() {
- return mOsuManager.getAvailableOSUs();
- }
-
- public void selectOsu(int id) {
- mOsuManager.setOSUSelection(id);
- }
- }
-
- private String getReadableTimeInSeconds(int timeSeconds) {
- long hours = TimeUnit.SECONDS.toHours(timeSeconds);
- long minutes = TimeUnit.SECONDS.toMinutes(timeSeconds) - TimeUnit.HOURS.toMinutes(hours);
- long seconds =
- timeSeconds - TimeUnit.HOURS.toSeconds(hours) - TimeUnit.MINUTES.toSeconds(minutes);
- if (hours > 0) {
- return String.format("%02d:%02d:%02d", hours, minutes, seconds);
- } else {
- return String.format("%ds", seconds);
- }
- }
-
- private void sendNotification(int count) {
- Notification.Builder builder =
- new Notification.Builder(this)
- .setContentTitle(String.format("%s OSU available", count))
- .setContentText("Choose one to connect")
- .setSmallIcon(android.R.drawable.ic_dialog_info)
- .setAutoCancel(false);
- Intent resultIntent = new Intent(this, MainActivity.class);
-
- TaskStackBuilder stackBuilder = TaskStackBuilder.create(this);
- stackBuilder.addParentStack(MainActivity.class);
- stackBuilder.addNextIntent(resultIntent);
- PendingIntent resultPendingIntent =
- stackBuilder.getPendingIntent(0, PendingIntent.FLAG_UPDATE_CURRENT);
- builder.setContentIntent(resultPendingIntent);
- NotificationManager notificationManager =
- (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
- notificationManager.notify(NOTIFICATION_ID, builder.build());
- }
-
- private void cancelNotification() {
- NotificationManager notificationManager =
- (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
- notificationManager.cancel(NOTIFICATION_ID);
- }
-
- private void sendDialogMessage(String message) {
-// sendNotificationMessage(message);
- this.message = message;
- }
-
- private void showDialog(String message) {
- AlertDialog.Builder builder = new AlertDialog.Builder(this);
- builder.setMessage(message)
- .setTitle("OSU");
- builder.setOnCancelListener(new DialogInterface.OnCancelListener() {
- @Override
- public void onCancel(DialogInterface dialogInterface) {
- dialogInterface.cancel();
- finish();
- }
- });
- AlertDialog dialog = builder.create();
- dialog.show();
- }
-
- private void sendNotificationMessage(String title) {
- Notification.Builder builder =
- new Notification.Builder(this)
- .setContentTitle(title)
- .setContentText("Click to dismiss.")
- .setSmallIcon(android.R.drawable.ic_dialog_info)
- .setAutoCancel(true);
- NotificationManager notificationManager =
- (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
- notificationManager.notify(NOTIFICATION_MESSAGE_ID, builder.build());
- }
-
- private static class OsuListAdapter2 extends ArrayAdapter {
- private Activity activity;
-
- public OsuListAdapter2(Activity activity, List osuDataList) {
- super(activity, R.layout.list_item, osuDataList);
- this.activity = activity;
- }
-
- @Override
- public View getView(int position, View convertView, ViewGroup parent) {
- View view = convertView;
- if (view == null) {
- view = LayoutInflater.from(getContext()).inflate(R.layout.list_item, parent, false);
- }
- OSUInfo osuData = getItem(position);
- TextView osuName = (TextView) view.findViewById(R.id.profile_name);
- osuName.setText(osuData.getName(LOCALE));
- TextView osuDetail = (TextView) view.findViewById(R.id.profile_detail);
- osuDetail.setText(osuData.getServiceDescription(LOCALE));
- ImageView osuIcon = (ImageView) view.findViewById(R.id.profile_logo);
- byte[] iconData = osuData.getIconFileElement().getIconData();
- osuIcon.setImageDrawable(
- new BitmapDrawable(activity.getResources(),
- BitmapFactory.decodeByteArray(iconData, 0, iconData.length)));
- return view;
- }
- }
-
- private void printOsuDataList(List osuDataList) {
- for (OSUInfo osuData : osuDataList) {
- Log.d("osu", String.format("OSUData:[%s][%s][%d]",
- osuData.getName(LOCALE), osuData.getServiceDescription(LOCALE),
- osuData.getOsuID()));
- }
- }
-
-}
diff --git a/packages/Osu/src/com/android/anqp/HSIconFileElement.java b/packages/Osu/src/com/android/anqp/HSIconFileElement.java
index c5c49cc00fa0b..28c597a5d47a5 100644
--- a/packages/Osu/src/com/android/anqp/HSIconFileElement.java
+++ b/packages/Osu/src/com/android/anqp/HSIconFileElement.java
@@ -1,5 +1,9 @@
package com.android.anqp;
+import android.os.Parcel;
+
+import com.android.hotspot2.Utils;
+
import java.net.ProtocolException;
import java.nio.ByteBuffer;
import java.nio.charset.StandardCharsets;
@@ -71,4 +75,17 @@ public class HSIconFileElement extends ANQPElement {
", type='" + mType + '\'' +
", iconData=" + mIconData.length + " bytes }";
}
+
+ public HSIconFileElement(Parcel in) {
+ super(Constants.ANQPElementType.HSIconFile);
+ mStatusCode = Utils.mapEnum(in.readInt(), StatusCode.class);
+ mType = in.readString();
+ mIconData = in.readBlob();
+ }
+
+ public void writeParcel(Parcel out) {
+ out.writeInt(mStatusCode.ordinal());
+ out.writeString(mType);
+ out.writeBlob(mIconData);
+ }
}
diff --git a/packages/Osu/src/com/android/anqp/I18Name.java b/packages/Osu/src/com/android/anqp/I18Name.java
index 0a16db74b86fd..b048228722c5a 100644
--- a/packages/Osu/src/com/android/anqp/I18Name.java
+++ b/packages/Osu/src/com/android/anqp/I18Name.java
@@ -1,5 +1,7 @@
package com.android.anqp;
+import android.os.Parcel;
+
import java.io.IOException;
import java.net.ProtocolException;
import java.nio.ByteBuffer;
@@ -77,4 +79,15 @@ public class I18Name {
public String toString() {
return mText + ':' + mLocale.getLanguage();
}
+
+ public I18Name(Parcel in) throws IOException {
+ mLanguage = in.readString();
+ mText = in.readString();
+ mLocale = Locale.forLanguageTag(mLanguage);
+ }
+
+ public void writeParcel(Parcel out) {
+ out.writeString(mLanguage);
+ out.writeString(mText);
+ }
}
diff --git a/packages/Osu/src/com/android/anqp/IconInfo.java b/packages/Osu/src/com/android/anqp/IconInfo.java
index 9e9f1eeba915e..ac507c7af948e 100644
--- a/packages/Osu/src/com/android/anqp/IconInfo.java
+++ b/packages/Osu/src/com/android/anqp/IconInfo.java
@@ -1,5 +1,7 @@
package com.android.anqp;
+import android.os.Parcel;
+
import java.net.ProtocolException;
import java.nio.ByteBuffer;
import java.nio.charset.StandardCharsets;
@@ -88,4 +90,20 @@ public class IconInfo {
", FileName='" + mFileName + '\'' +
'}';
}
+
+ public IconInfo(Parcel in) {
+ mWidth = in.readInt();
+ mHeight = in.readInt();
+ mLanguage = in.readString();
+ mIconType = in.readString();
+ mFileName = in.readString();
+ }
+
+ public void writeParcel(Parcel out) {
+ out.writeInt(mWidth);
+ out.writeInt(mHeight);
+ out.writeString(mLanguage);
+ out.writeString(mIconType);
+ out.writeString(mFileName);
+ }
}
diff --git a/packages/Osu/src/com/android/anqp/OSUProvider.java b/packages/Osu/src/com/android/anqp/OSUProvider.java
index e2669d433c1e4..3724cf06d15bd 100644
--- a/packages/Osu/src/com/android/anqp/OSUProvider.java
+++ b/packages/Osu/src/com/android/anqp/OSUProvider.java
@@ -1,5 +1,10 @@
package com.android.anqp;
+import android.os.Parcel;
+
+import com.android.hotspot2.Utils;
+
+import java.io.IOException;
import java.net.ProtocolException;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
@@ -155,4 +160,54 @@ public class OSUProvider {
", serviceDescriptions=" + mServiceDescriptions +
'}';
}
+
+ public OSUProvider(Parcel in) throws IOException {
+ mSSID = in.readString();
+ int nameCount = in.readInt();
+ mNames = new ArrayList<>(nameCount);
+ for (int n = 0; n < nameCount; n++) {
+ mNames.add(new I18Name(in));
+ }
+ mOSUServer = in.readString();
+ int methodCount = in.readInt();
+ mOSUMethods = new ArrayList<>(methodCount);
+ for (int n = 0; n < methodCount; n++) {
+ mOSUMethods.add(Utils.mapEnum(in.readInt(), OSUMethod.class));
+ }
+ int iconCount = in.readInt();
+ mIcons = new ArrayList<>(iconCount);
+ for (int n = 0; n < iconCount; n++) {
+ mIcons.add(new IconInfo(in));
+ }
+ mOsuNai = in.readString();
+ int serviceCount = in.readInt();
+ mServiceDescriptions = new ArrayList<>(serviceCount);
+ for (int n = 0; n < serviceCount; n++) {
+ mServiceDescriptions.add(new I18Name(in));
+ }
+ mHashCode = in.readInt();
+ }
+
+ public void writeParcel(Parcel out) {
+ out.writeString(mSSID);
+ out.writeInt(mNames.size());
+ for (I18Name name : mNames) {
+ name.writeParcel(out);
+ }
+ out.writeString(mOSUServer);
+ out.writeInt(mOSUMethods.size());
+ for (OSUMethod method : mOSUMethods) {
+ out.writeInt(method.ordinal());
+ }
+ out.writeInt(mIcons.size());
+ for (IconInfo iconInfo : mIcons) {
+ iconInfo.writeParcel(out);
+ }
+ out.writeString(mOsuNai);
+ out.writeInt(mServiceDescriptions.size());
+ for (I18Name serviceDescription : mServiceDescriptions) {
+ serviceDescription.writeParcel(out);
+ }
+ out.writeInt(mHashCode);
+ }
}
diff --git a/packages/Osu/src/com/android/hotspot2/AppBridge.java b/packages/Osu/src/com/android/hotspot2/AppBridge.java
index 95f5970bc74a7..81542f7fc466c 100644
--- a/packages/Osu/src/com/android/hotspot2/AppBridge.java
+++ b/packages/Osu/src/com/android/hotspot2/AppBridge.java
@@ -2,13 +2,9 @@ package com.android.hotspot2;
import android.content.Context;
import android.content.Intent;
-import android.os.UserHandle;
-import com.android.hotspot2.osu.OSUInfo;
import com.android.hotspot2.osu.OSUOperationStatus;
-import java.util.List;
-
public class AppBridge {
public static final String ACTION_OSU_NOTIFICATION = "com.android.hotspot2.OSU_NOTIFICATION";
public static final String OSU_COUNT = "osu-count";
@@ -28,7 +24,7 @@ public class AppBridge {
mContext = context;
}
- public void showOsuCount(int osuCount, List osus) {
+ public void showOsuCount(int osuCount) {
Intent intent = new Intent(ACTION_OSU_NOTIFICATION);
intent.putExtra(OSU_COUNT, osuCount);
intent.setFlags(
diff --git a/packages/Osu/src/com/android/hotspot2/Utils.java b/packages/Osu/src/com/android/hotspot2/Utils.java
index 100b967f6cf30..880007f6e392b 100644
--- a/packages/Osu/src/com/android/hotspot2/Utils.java
+++ b/packages/Osu/src/com/android/hotspot2/Utils.java
@@ -3,6 +3,9 @@ package com.android.hotspot2;
import com.android.anqp.Constants;
import java.nio.ByteBuffer;
+import java.nio.charset.CharacterCodingException;
+import java.nio.charset.CharsetDecoder;
+import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Collection;
@@ -94,6 +97,14 @@ public abstract class Utils {
return prefix;
}
+ public static String toIpString(int leIp) {
+ return String.format("%d.%d.%d.%d",
+ leIp & BYTE_MASK,
+ (leIp >> 8) & BYTE_MASK,
+ (leIp >> 16) & BYTE_MASK,
+ (leIp >> 24) & BYTE_MASK);
+ }
+
public static String bssidsToString(Collection bssids) {
StringBuilder sb = new StringBuilder();
for (Long bssid : bssids) {
@@ -274,16 +285,107 @@ public abstract class Utils {
c.get(Calendar.SECOND));
}
- public static String unquote(String s) {
- if (s == null) {
- return null;
- } else if (s.length() > 1 && s.startsWith("\"") && s.endsWith("\"")) {
- return s.substring(1, s.length() - 1);
- } else {
- return s;
+ /**
+ * Decode a wpa_supplicant SSID. wpa_supplicant uses double quotes around plain strings, or
+ * expects a hex-string if no quotes appear.
+ * For Ascii encoded string, any octet < 32 or > 127 is encoded as
+ * a "\x" followed by the hex representation of the octet.
+ * Exception chars are ", \, \e, \n, \r, \t which are escaped by a \
+ * See src/utils/common.c for the implementation in the supplicant.
+ *
+ * @param ssid The SSID from the config.
+ * @return The actual string content of the SSID
+ */
+ public static String decodeSsid(String ssid) {
+ if (ssid.length() <= 1) {
+ return ssid;
+ } else if (ssid.startsWith("\"") && ssid.endsWith("\"")) {
+ return unescapeSsid(ssid.substring(1, ssid.length() - 1));
+ } else if ((ssid.length() & 1) == 1) {
+ return ssid;
+ }
+
+ byte[] codepoints;
+ try {
+ codepoints = new byte[ssid.length() / 2];
+ for (int n = 0; n < ssid.length(); n += 2) {
+ codepoints[n / 2] = (byte) decodeHexPair(ssid, n);
+ }
+ } catch (NumberFormatException nfe) {
+ return ssid;
+ }
+
+ try {
+ CharsetDecoder decoder = StandardCharsets.UTF_8.newDecoder();
+ return decoder.decode(ByteBuffer.wrap(codepoints)).toString();
+ } catch (CharacterCodingException cce) {
+ /* Do nothing, try LATIN-1 */
+ }
+ try {
+ CharsetDecoder decoder = StandardCharsets.ISO_8859_1.newDecoder();
+ return decoder.decode(ByteBuffer.wrap(codepoints)).toString();
+ } catch (CharacterCodingException cce) { // Should not be possible.
+ return ssid;
}
}
+ private static String unescapeSsid(String s) {
+ StringBuilder sb = new StringBuilder();
+ for (int n = 0; n < s.length(); n++) {
+ char ch = s.charAt(n);
+ if (ch != '\\' || n >= s.length() - 1) {
+ sb.append(ch);
+ } else {
+ n++;
+ ch = s.charAt(n);
+ switch (ch) {
+ case '"':
+ case '\\':
+ default:
+ sb.append(ch);
+ break;
+ case 'e':
+ sb.append((char) 27); // Escape char
+ break;
+ case 'n':
+ sb.append('\n');
+ break;
+ case 'r':
+ sb.append('\r');
+ break;
+ case 't':
+ sb.append('\t');
+ break;
+ case 'x':
+ if (s.length() - n < 3) {
+ sb.append('\\').append(ch);
+ } else {
+ n++;
+ sb.append((char) decodeHexPair(s, n));
+ n++;
+ }
+ break;
+ }
+ }
+ }
+ return sb.toString();
+ }
+
+ private static int decodeHexPair(String s, int position) {
+ return fromHex(s.charAt(position)) << 4 | fromHex(s.charAt(position + 1));
+ }
+
+ private static int fromHex(char ch) {
+ if (ch >= '0' && ch <= '9') {
+ return ch - '0';
+ } else if (ch >= 'A' && ch <= 'F') {
+ return ch - 'A' + 10;
+ } else if (ch >= 'a' && ch <= 'f') {
+ return ch - 'a' + 10;
+ } else {
+ throw new NumberFormatException(String.format("Not hex: '%c'", ch));
+ }
+ }
public static void delay(long ms) {
long until = System.currentTimeMillis() + ms;
@@ -297,4 +399,9 @@ public abstract class Utils {
} catch (InterruptedException ie) { /**/ }
}
}
+
+ public static > T mapEnum(int ordinal, Class enumClass) {
+ T[] constants = enumClass.getEnumConstants();
+ return ordinal >= 0 && ordinal < constants.length ? constants[ordinal]: null;
+ }
}
diff --git a/packages/Osu/src/com/android/hotspot2/WifiNetworkAdapter.java b/packages/Osu/src/com/android/hotspot2/WifiNetworkAdapter.java
deleted file mode 100644
index 84cafa219595d..0000000000000
--- a/packages/Osu/src/com/android/hotspot2/WifiNetworkAdapter.java
+++ /dev/null
@@ -1,394 +0,0 @@
-package com.android.hotspot2;
-
-import android.content.Context;
-import android.content.Intent;
-import android.net.CaptivePortal;
-import android.net.ConnectivityManager;
-import android.net.ICaptivePortal;
-import android.net.Network;
-import android.net.wifi.PasspointManagementObjectDefinition;
-import android.net.wifi.WifiConfiguration;
-import android.net.wifi.WifiEnterpriseConfig;
-import android.net.wifi.WifiInfo;
-import android.net.wifi.WifiManager;
-import android.util.Log;
-
-import com.android.configparse.ConfigBuilder;
-import com.android.hotspot2.omadm.MOManager;
-import com.android.hotspot2.omadm.MOTree;
-import com.android.hotspot2.omadm.OMAConstants;
-import com.android.hotspot2.omadm.OMAException;
-import com.android.hotspot2.omadm.OMAParser;
-import com.android.hotspot2.osu.OSUCertType;
-import com.android.hotspot2.osu.OSUInfo;
-import com.android.hotspot2.osu.OSUManager;
-import com.android.hotspot2.osu.commands.MOData;
-import com.android.hotspot2.pps.HomeSP;
-
-import org.xml.sax.SAXException;
-
-import java.io.IOException;
-import java.net.URL;
-import java.security.GeneralSecurityException;
-import java.security.PrivateKey;
-import java.security.cert.X509Certificate;
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-
-public class WifiNetworkAdapter {
- private final Context mContext;
- private final OSUManager mOSUManager;
- private final Map mPasspointConfigs = new HashMap<>();
-
- private static class PasspointConfig {
- private final WifiConfiguration mWifiConfiguration;
- private final MOTree mMOTree;
- private final HomeSP mHomeSP;
-
- private PasspointConfig(WifiConfiguration config) throws IOException, SAXException {
- mWifiConfiguration = config;
- OMAParser omaParser = new OMAParser();
- mMOTree = omaParser.parse(config.getMoTree(), OMAConstants.PPS_URN);
- List spList = MOManager.buildSPs(mMOTree);
- if (spList.size() != 1) {
- throw new OMAException("Expected exactly one HomeSP, got " + spList.size());
- }
- mHomeSP = spList.iterator().next();
- }
-
- public WifiConfiguration getWifiConfiguration() {
- return mWifiConfiguration;
- }
-
- public HomeSP getHomeSP() {
- return mHomeSP;
- }
-
- public MOTree getmMOTree() {
- return mMOTree;
- }
- }
-
- public WifiNetworkAdapter(Context context, OSUManager osuManager) {
- mOSUManager = osuManager;
- mContext = context;
- }
-
- public void initialize() {
- loadAllSps();
- }
-
- public void networkConfigChange(WifiConfiguration configuration) {
- // !!! Watch out for changed r2 configs - remove the MO.
- loadAllSps();
- }
-
- private void loadAllSps() {
- WifiManager wifiManager = (WifiManager) mContext.getSystemService(Context.WIFI_SERVICE);
- int count = 0;
- for (WifiConfiguration config : wifiManager.getPrivilegedConfiguredNetworks()) {
- String moTree = config.getMoTree();
- if (moTree != null) {
- try {
- mPasspointConfigs.put(config.FQDN, new PasspointConfig(config));
- count++;
- } catch (IOException | SAXException e) {
- Log.w(OSUManager.TAG, "Failed to parse MO: " + e);
- }
- }
- }
- Log.d(OSUManager.TAG, "Loaded " + count + " SPs");
- // !!! Detect adds/deletes
- }
-
- public Collection getLoadedSPs() {
- List homeSPs = new ArrayList<>();
- for (PasspointConfig config : mPasspointConfigs.values()) {
- homeSPs.add(config.getHomeSP());
- }
- return homeSPs;
- }
-
- public MOTree getMOTree(HomeSP homeSP) {
- PasspointConfig config = mPasspointConfigs.get(homeSP.getFQDN());
- return config != null ? config.getmMOTree() : null;
- }
-
- public void launchBrowser(URL target, Network network, URL endRedirect) {
- Log.d(OSUManager.TAG, "Browser to " + target + ", land at " + endRedirect);
-
- final Intent intent = new Intent(
- ConnectivityManager.ACTION_CAPTIVE_PORTAL_SIGN_IN);
- intent.putExtra(ConnectivityManager.EXTRA_NETWORK, network);
- intent.putExtra(ConnectivityManager.EXTRA_CAPTIVE_PORTAL,
- new CaptivePortal(new ICaptivePortal.Stub() {
- @Override
- public void appResponse(int response) {
- }
- }));
- //intent.setData(Uri.parse(target.toString())); !!! Doesn't work!
- intent.putExtra(ConnectivityManager.EXTRA_CAPTIVE_PORTAL_URL, target.toString());
- intent.setFlags(
- Intent.FLAG_ACTIVITY_BROUGHT_TO_FRONT | Intent.FLAG_ACTIVITY_NEW_TASK);
- mContext.startActivity(intent);
- }
-
- public int addSP(String xml) throws IOException, SAXException {
- WifiManager wifiManager = (WifiManager) mContext.getSystemService(Context.WIFI_SERVICE);
- return wifiManager.addPasspointManagementObject(xml);
- }
-
- public int modifySP(HomeSP homeSP, Collection mods) throws IOException {
- WifiManager wifiManager = (WifiManager) mContext.getSystemService(Context.WIFI_SERVICE);
- List defMods = new ArrayList<>(mods.size());
- for (MOData mod : mods) {
- defMods.add(new PasspointManagementObjectDefinition(mod.getBaseURI(),
- mod.getURN(), mod.getMOTree().toXml()));
- }
- return wifiManager.modifyPasspointManagementObject(homeSP.getFQDN(), defMods);
- }
-
- public Network getCurrentNetwork() {
- WifiManager wifiManager = (WifiManager) mContext.getSystemService(Context.WIFI_SERVICE);
- return wifiManager.getCurrentNetwork();
- }
-
- public WifiConfiguration getActiveWifiConfig() {
- WifiInfo wifiInfo = getConnectionInfo();
- if (wifiInfo == null) {
- return null;
- }
- WifiManager wifiManager = (WifiManager) mContext.getSystemService(Context.WIFI_SERVICE);
- for (WifiConfiguration config : wifiManager.getConfiguredNetworks()) {
- if (config.networkId == wifiInfo.getNetworkId()) {
- return config;
- }
- }
- return null;
- }
-
- public WifiInfo getConnectionInfo() {
- WifiManager wifiManager = (WifiManager) mContext.getSystemService(Context.WIFI_SERVICE);
- return wifiManager.getConnectionInfo();
- }
-
- public PasspointMatch matchProviderWithCurrentNetwork(String fqdn) {
- WifiManager wifiManager = (WifiManager) mContext.getSystemService(Context.WIFI_SERVICE);
- int ordinal = wifiManager.matchProviderWithCurrentNetwork(fqdn);
- return ordinal >= 0 && ordinal < PasspointMatch.values().length ?
- PasspointMatch.values()[ordinal] : null;
- }
-
- public WifiConfiguration getWifiConfig(HomeSP homeSP) {
- PasspointConfig passpointConfig = mPasspointConfigs.get(homeSP.getFQDN());
- return passpointConfig != null ? passpointConfig.getWifiConfiguration() : null;
- }
-
- public HomeSP getHomeSP(WifiConfiguration configuration) {
- if (configuration.isPasspoint()) {
- PasspointConfig config = mPasspointConfigs.get(configuration.FQDN);
- return config != null ? config.getHomeSP() : null;
- }
- return null;
- }
-
- public HomeSP getCurrentSP() {
- PasspointConfig passpointConfig = getActivePasspointConfig();
- return passpointConfig != null ? passpointConfig.getHomeSP() : null;
- }
-
- private PasspointConfig getActivePasspointConfig() {
- WifiInfo wifiInfo = getConnectionInfo();
- if (wifiInfo == null) {
- return null;
- }
-
- for (PasspointConfig passpointConfig : mPasspointConfigs.values()) {
- if (passpointConfig.getWifiConfiguration().networkId == wifiInfo.getNetworkId()) {
- return passpointConfig;
- }
- }
- return null;
- }
-
- public void doIconQuery(long bssid, String fileName) {
- WifiManager wifiManager = (WifiManager) mContext.getSystemService(Context.WIFI_SERVICE);
- Log.d("ZXZ", String.format("Icon query for %012x '%s'", bssid, fileName));
- wifiManager.queryPasspointIcon(bssid, fileName);
- }
-
- public Integer addNetwork(HomeSP homeSP, Map> certs,
- PrivateKey privateKey, Network osuNetwork)
- throws IOException, GeneralSecurityException {
-
- List aaaTrust = certs.get(OSUCertType.AAA);
- if (aaaTrust.isEmpty()) {
- aaaTrust = certs.get(OSUCertType.CA); // Get the CAs from the EST flow.
- }
-
- WifiConfiguration config = ConfigBuilder.buildConfig(homeSP,
- aaaTrust.iterator().next(),
- certs.get(OSUCertType.Client), privateKey);
-
- WifiManager wifiManager = (WifiManager) mContext.getSystemService(Context.WIFI_SERVICE);
- int nwkId = wifiManager.addNetwork(config);
- boolean saved = false;
- if (nwkId >= 0) {
- saved = wifiManager.saveConfiguration();
- }
- Log.d(OSUManager.TAG, "Wifi configuration " + nwkId +
- " " + (saved ? "saved" : "not saved"));
-
- if (saved) {
- reconnect(osuNetwork, nwkId);
- return nwkId;
- } else {
- return null;
- }
- }
-
- public void updateNetwork(HomeSP homeSP, X509Certificate caCert,
- List clientCerts, PrivateKey privateKey)
- throws IOException, GeneralSecurityException {
-
- WifiConfiguration config = getWifiConfig(homeSP);
- if (config == null) {
- throw new IOException("Failed to find matching network config");
- }
- Log.d(OSUManager.TAG, "Found matching config " + config.networkId + ", updating");
-
- WifiEnterpriseConfig enterpriseConfig = config.enterpriseConfig;
- WifiConfiguration newConfig = ConfigBuilder.buildConfig(homeSP,
- caCert != null ? caCert : enterpriseConfig.getCaCertificate(),
- clientCerts, privateKey);
- newConfig.networkId = config.networkId;
-
- WifiManager wifiManager = (WifiManager) mContext.getSystemService(Context.WIFI_SERVICE);
- wifiManager.save(newConfig, null);
- wifiManager.saveConfiguration();
- }
-
- /**
- * Connect to an OSU provisioning network. The connection should not bring down other existing
- * connection and the network should not be made the default network since the connection
- * is solely for sign up and is neither intended for nor likely provides access to any
- * generic resources.
- *
- * @param osuInfo The OSU info object that defines the parameters for the network. An OSU
- * network is either an open network, or, if the OSU NAI is set, an "OSEN"
- * network, which is an anonymous EAP-TLS network with special keys.
- * @param info An opaque string that is passed on to any user notification. The string is used
- * for the name of the service provider.
- * @return an Integer holding the network-id of the just added network configuration, or null
- * if the network existed prior to this call (was not added by the OSU infrastructure).
- * The value will be used at the end of the OSU flow to delete the network as applicable.
- * @throws IOException Issues:
- * 1. The network id is not returned. addNetwork cannot be called from here since the method
- * runs in the context of the app and doesn't have the appropriate permission.
- * 2. The connection is not immediately usable if the network was not previously selected
- * manually.
- */
- public Integer connect(OSUInfo osuInfo, final String info) throws IOException {
- WifiManager wifiManager = (WifiManager) mContext.getSystemService(Context.WIFI_SERVICE);
-
- WifiConfiguration config = new WifiConfiguration();
- config.SSID = '"' + osuInfo.getOsuSsid() + '"';
- if (osuInfo.getOSUBssid() != 0) {
- config.BSSID = Utils.macToString(osuInfo.getOSUBssid());
- Log.d(OSUManager.TAG, String.format("Setting BSSID of '%s' to %012x",
- osuInfo.getOsuSsid(), osuInfo.getOSUBssid()));
- }
-
- if (osuInfo.getOSUProvider().getOsuNai() == null) {
- config.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.NONE);
- } else {
- config.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.OSEN);
- config.allowedProtocols.set(WifiConfiguration.Protocol.OSEN);
- config.allowedPairwiseCiphers.set(WifiConfiguration.PairwiseCipher.CCMP);
- config.allowedGroupCiphers.set(WifiConfiguration.GroupCipher.GTK_NOT_USED);
- config.enterpriseConfig = new WifiEnterpriseConfig();
- config.enterpriseConfig.setEapMethod(WifiEnterpriseConfig.Eap.UNAUTH_TLS);
- config.enterpriseConfig.setIdentity(osuInfo.getOSUProvider().getOsuNai());
- // !!! OSEN CA Cert???
- }
-
- int networkId = wifiManager.addNetwork(config);
- if (wifiManager.enableNetwork(networkId, true)) {
- return networkId;
- } else {
- return null;
- }
-
- /* sequence of addNetwork(), enableNetwork(), saveConfiguration() and reconnect()
- wifiManager.connect(config, new WifiManager.ActionListener() {
- @Override
- public void onSuccess() {
- // Connection event comes from network change intent registered in initialize
- }
-
- @Override
- public void onFailure(int reason) {
- mOSUManager.notifyUser(OSUOperationStatus.ProvisioningFailure,
- "Cannot connect to OSU network: " + reason, info);
- }
- });
- return null;
-
- /*
- try {
- int nwkID = wifiManager.addOrUpdateOSUNetwork(config);
- if (nwkID == WifiConfiguration.INVALID_NETWORK_ID) {
- throw new IOException("Failed to add OSU network");
- }
- wifiManager.enableNetwork(nwkID, false);
- wifiManager.reconnect();
- return nwkID;
- }
- catch (SecurityException se) {
- Log.d("ZXZ", "Blah: " + se, se);
- wifiManager.connect(config, new WifiManager.ActionListener() {
- @Override
- public void onSuccess() {
- // Connection event comes from network change intent registered in initialize
- }
-
- @Override
- public void onFailure(int reason) {
- mOSUManager.notifyUser(OSUOperationStatus.ProvisioningFailure,
- "Cannot connect to OSU network: " + reason, info);
- }
- });
- return null;
- }
- */
- }
-
- private void reconnect(Network osuNetwork, int newNwkId) {
- WifiManager wifiManager = (WifiManager) mContext.getSystemService(Context.WIFI_SERVICE);
- if (osuNetwork != null) {
- wifiManager.disableNetwork(osuNetwork.netId);
- }
- if (newNwkId != WifiConfiguration.INVALID_NETWORK_ID) {
- wifiManager.enableNetwork(newNwkId, true);
- }
- }
-
- public void deleteNetwork(int id) {
- WifiManager wifiManager = (WifiManager) mContext.getSystemService(Context.WIFI_SERVICE);
- wifiManager.disableNetwork(id);
- wifiManager.forget(id, null);
- }
-
- /**
- * Set the re-authentication hold off time for the current network
- *
- * @param holdoff hold off time in milliseconds
- * @param ess set if the hold off pertains to an ESS rather than a BSS
- */
- public void setHoldoffTime(long holdoff, boolean ess) {
-
- }
-}
diff --git a/packages/Osu/src/com/android/hotspot2/app/IOSUAccessor.aidl b/packages/Osu/src/com/android/hotspot2/app/IOSUAccessor.aidl
new file mode 100644
index 0000000000000..500dd2ee32c48
--- /dev/null
+++ b/packages/Osu/src/com/android/hotspot2/app/IOSUAccessor.aidl
@@ -0,0 +1,8 @@
+package com.android.hotspot2.app;
+
+import com.android.hotspot2.app.OSUData;
+
+interface IOSUAccessor {
+ List getOsuData();
+ void selectOsu(int id);
+}
diff --git a/packages/Osu/src/com/android/hotspot2/app/LocalServiceBinder.java b/packages/Osu/src/com/android/hotspot2/app/LocalServiceBinder.java
new file mode 100644
index 0000000000000..8801839fe2624
--- /dev/null
+++ b/packages/Osu/src/com/android/hotspot2/app/LocalServiceBinder.java
@@ -0,0 +1,15 @@
+package com.android.hotspot2.app;
+
+import android.os.Binder;
+
+public class LocalServiceBinder extends Binder {
+ private final OSUService mDelegate;
+
+ public LocalServiceBinder(OSUService delegate) {
+ mDelegate = delegate;
+ }
+
+ public OSUService getService() {
+ return mDelegate;
+ }
+}
diff --git a/packages/Osu/src/com/android/hotspot2/app/MainActivity.java b/packages/Osu/src/com/android/hotspot2/app/MainActivity.java
new file mode 100644
index 0000000000000..ae0a45c78ff9c
--- /dev/null
+++ b/packages/Osu/src/com/android/hotspot2/app/MainActivity.java
@@ -0,0 +1,303 @@
+package com.android.hotspot2.app;
+
+import android.app.Activity;
+import android.app.AlertDialog;
+import android.app.Notification;
+import android.app.NotificationManager;
+import android.app.PendingIntent;
+import android.app.TaskStackBuilder;
+import android.content.ComponentName;
+import android.content.DialogInterface;
+import android.content.Intent;
+import android.content.ServiceConnection;
+import android.graphics.BitmapFactory;
+import android.graphics.drawable.BitmapDrawable;
+import android.os.Bundle;
+import android.os.IBinder;
+import android.util.Log;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.AdapterView;
+import android.widget.ArrayAdapter;
+import android.widget.ImageView;
+import android.widget.ListView;
+import android.widget.TextView;
+
+import com.android.hotspot2.AppBridge;
+import com.android.hotspot2.R;
+import com.android.hotspot2.osu.OSUManager;
+
+import java.util.Collections;
+import java.util.List;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Main activity.
+ */
+public class MainActivity extends Activity {
+ private static final int NOTIFICATION_ID = 0; // Used for OSU count
+ private static final int NOTIFICATION_MESSAGE_ID = 1; // Used for other messages
+ private static final String ACTION_SVC_BOUND = "SVC_BOUND";
+
+ private volatile OSUService mLocalService;
+
+ private final ServiceConnection mConnection = new ServiceConnection() {
+ @Override
+ public void onServiceConnected(ComponentName name, IBinder service) {
+ LocalServiceBinder binder = (LocalServiceBinder) service;
+ mLocalService = binder.getService();
+ showOsuSelection(mLocalService);
+ }
+
+ @Override
+ public void onServiceDisconnected(ComponentName name) {
+ mLocalService = null;
+ }
+ };
+
+ private ListView osuListView;
+ private OsuListAdapter osuListAdapter;
+ private String message;
+
+ public MainActivity() {
+
+ }
+
+ @Override
+ protected void onStop() {
+ super.onStop();
+ if (mLocalService != null) {
+ unbindService(mConnection);
+ mLocalService = null;
+ }
+ }
+
+ @Override
+ protected void onResume() {
+ super.onResume();
+ if (message != null) {
+ showDialog(message);
+ message = null;
+ }
+ }
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ final Intent intent = getIntent();
+ Bundle bundle = intent.getExtras();
+
+ if (intent.getAction() == null) {
+ if (mLocalService == null) {
+ bindService(new Intent(this, OSUService.class), mConnection, 0);
+ }
+ } else if (intent.getAction().equals(AppBridge.ACTION_OSU_NOTIFICATION)) {
+ if (bundle == null) {
+ Log.d(OSUManager.TAG, "No parameters for OSU notification");
+ return;
+ }
+ if (bundle.containsKey(AppBridge.OSU_COUNT)) {
+ showOsuCount(bundle.getInt("osu-count", 0), Collections.emptyList());
+ } else if (bundle.containsKey(AppBridge.PROV_SUCCESS)) {
+ showStatus(bundle.getBoolean(AppBridge.PROV_SUCCESS),
+ bundle.getString(AppBridge.SP_NAME),
+ bundle.getString(AppBridge.PROV_MESSAGE),
+ null);
+ } else if (bundle.containsKey(AppBridge.DEAUTH)) {
+ showDeauth(bundle.getString(AppBridge.SP_NAME),
+ bundle.getBoolean(AppBridge.DEAUTH),
+ bundle.getInt(AppBridge.DEAUTH_DELAY),
+ bundle.getString(AppBridge.DEAUTH_URL));
+ }
+ }
+ }
+
+ private void showOsuSelection(final OSUService osuService) {
+ List osuData = osuService.getOsuData();
+
+ setContentView(R.layout.activity_main);
+ Log.d("osu", "osu count:" + osuData.size());
+ View noOsuView = findViewById(R.id.no_osu);
+ if (osuData.size() > 0) {
+ noOsuView.setVisibility(View.GONE);
+ osuListAdapter = new OsuListAdapter(this, osuData);
+ osuListView = (ListView) findViewById(R.id.profile_list);
+ osuListView.setAdapter(osuListAdapter);
+ osuListView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
+ @Override
+ public void onItemClick(AdapterView> adapterView, View view, int position, long id) {
+ OSUData osuData = (OSUData) adapterView.getAdapter().getItem(position);
+ Log.d("osu", "launch osu:" + osuData.getName()
+ + " id:" + osuData.getId());
+ osuService.selectOsu(osuData.getId());
+ finish();
+ }
+ });
+ } else {
+ noOsuView.setVisibility(View.VISIBLE);
+ }
+ }
+
+ private void showOsuCount(int osuCount, List osus) {
+ if (osuCount > 0) {
+ printOsuDataList(osus);
+ sendNotification(osuCount);
+ } else {
+ cancelNotification();
+ }
+ finish();
+ }
+
+ private void showStatus(boolean provSuccess, String spName, String provMessage,
+ String remoteStatus) {
+ if (provSuccess) {
+ sendDialogMessage(
+ String.format("Credentials for %s was successfully installed", spName));
+ } else {
+ if (spName != null) {
+ if (remoteStatus != null) {
+ sendDialogMessage(
+ String.format("Failed to install credentials for %s: %s: %s",
+ spName, provMessage, remoteStatus));
+ } else {
+ sendDialogMessage(
+ String.format("Failed to install credentials for %s: %s",
+ spName, provMessage));
+ }
+ } else {
+ sendDialogMessage(
+ String.format("Failed to contact OSU: %s", provMessage));
+ }
+ }
+ }
+
+ private void showDeauth(String spName, boolean ess, int delay, String url) {
+ String delayReadable = getReadableTimeInSeconds(delay);
+ if (ess) {
+ if (delay > 60) {
+ sendDialogMessage(
+ String.format("There is an issue connecting to %s [for the next %s]. " +
+ "Please visit %s for details", spName, delayReadable, url));
+ } else {
+ sendDialogMessage(
+ String.format("There is an issue connecting to %s. " +
+ "Please visit %s for details", spName, url));
+ }
+ } else {
+ sendDialogMessage(
+ String.format("There is an issue with the closest Access Point for %s. " +
+ "You may wait %s or move to another Access Point to " +
+ "regain access. Please visit %s for details.",
+ spName, delayReadable, url));
+ }
+ }
+
+ private String getReadableTimeInSeconds(int timeSeconds) {
+ long hours = TimeUnit.SECONDS.toHours(timeSeconds);
+ long minutes = TimeUnit.SECONDS.toMinutes(timeSeconds) - TimeUnit.HOURS.toMinutes(hours);
+ long seconds =
+ timeSeconds - TimeUnit.HOURS.toSeconds(hours) - TimeUnit.MINUTES.toSeconds(minutes);
+ if (hours > 0) {
+ return String.format("%02d:%02d:%02d", hours, minutes, seconds);
+ } else {
+ return String.format("%ds", seconds);
+ }
+ }
+
+ private void sendNotification(int count) {
+ Notification.Builder builder =
+ new Notification.Builder(this)
+ .setContentTitle(String.format("%s OSU available", count))
+ .setContentText("Choose one to connect")
+ .setSmallIcon(android.R.drawable.ic_dialog_info)
+ .setAutoCancel(false);
+ Intent resultIntent = new Intent(this, MainActivity.class);
+
+ TaskStackBuilder stackBuilder = TaskStackBuilder.create(this);
+ stackBuilder.addParentStack(MainActivity.class);
+ stackBuilder.addNextIntent(resultIntent);
+ PendingIntent resultPendingIntent =
+ stackBuilder.getPendingIntent(0, PendingIntent.FLAG_UPDATE_CURRENT);
+ builder.setContentIntent(resultPendingIntent);
+ NotificationManager notificationManager =
+ (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
+ notificationManager.notify(NOTIFICATION_ID, builder.build());
+ }
+
+ private void cancelNotification() {
+ NotificationManager notificationManager =
+ (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
+ notificationManager.cancel(NOTIFICATION_ID);
+ }
+
+ private void sendDialogMessage(String message) {
+// sendNotificationMessage(message);
+ this.message = message;
+ }
+
+ private void showDialog(String message) {
+ AlertDialog.Builder builder = new AlertDialog.Builder(this);
+ builder.setMessage(message)
+ .setTitle("OSU");
+ builder.setOnCancelListener(new DialogInterface.OnCancelListener() {
+ @Override
+ public void onCancel(DialogInterface dialogInterface) {
+ dialogInterface.cancel();
+ finish();
+ }
+ });
+ AlertDialog dialog = builder.create();
+ dialog.show();
+ }
+
+ private void sendNotificationMessage(String title) {
+ Notification.Builder builder =
+ new Notification.Builder(this)
+ .setContentTitle(title)
+ .setContentText("Click to dismiss.")
+ .setSmallIcon(android.R.drawable.ic_dialog_info)
+ .setAutoCancel(true);
+ NotificationManager notificationManager =
+ (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
+ notificationManager.notify(NOTIFICATION_MESSAGE_ID, builder.build());
+ }
+
+ private static class OsuListAdapter extends ArrayAdapter {
+ private Activity activity;
+
+ public OsuListAdapter(Activity activity, List osuDataList) {
+ super(activity, R.layout.list_item, osuDataList);
+ this.activity = activity;
+ }
+
+ @Override
+ public View getView(int position, View convertView, ViewGroup parent) {
+ View view = convertView;
+ if (view == null) {
+ view = LayoutInflater.from(getContext()).inflate(R.layout.list_item, parent, false);
+ }
+ OSUData osuData = getItem(position);
+ TextView osuName = (TextView) view.findViewById(R.id.profile_name);
+ osuName.setText(osuData.getName());
+ TextView osuDetail = (TextView) view.findViewById(R.id.profile_detail);
+ osuDetail.setText(osuData.getServiceDescription());
+ ImageView osuIcon = (ImageView) view.findViewById(R.id.profile_logo);
+ byte[] iconData = osuData.getIconData();
+ osuIcon.setImageDrawable(
+ new BitmapDrawable(activity.getResources(),
+ BitmapFactory.decodeByteArray(iconData, 0, iconData.length)));
+ return view;
+ }
+ }
+
+ private void printOsuDataList(List osuDataList) {
+ for (OSUData osuData : osuDataList) {
+ Log.d("osu", String.format("OSUData:[%s][%s][%d]",
+ osuData.getName(), osuData.getServiceDescription(),
+ osuData.getId()));
+ }
+ }
+
+}
diff --git a/packages/Osu/src/com/android/hotspot2/app/OSUData.aidl b/packages/Osu/src/com/android/hotspot2/app/OSUData.aidl
new file mode 100644
index 0000000000000..3407f47cbd627
--- /dev/null
+++ b/packages/Osu/src/com/android/hotspot2/app/OSUData.aidl
@@ -0,0 +1,4 @@
+package com.android.hotspot2.app;
+
+parcelable OSUData;
+
diff --git a/packages/Osu/src/com/android/hotspot2/app/OSUData.java b/packages/Osu/src/com/android/hotspot2/app/OSUData.java
new file mode 100644
index 0000000000000..17cc49b773348
--- /dev/null
+++ b/packages/Osu/src/com/android/hotspot2/app/OSUData.java
@@ -0,0 +1,69 @@
+package com.android.hotspot2.app;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import com.android.hotspot2.flow.OSUInfo;
+import com.android.hotspot2.osu.OSUManager;
+
+public class OSUData implements Parcelable {
+ private final String mName;
+ private final String mServiceDescription;
+ private final byte[] mIconData;
+ private final int mId;
+
+ public OSUData(OSUInfo osuInfo) {
+ mName = osuInfo.getName(OSUManager.LOCALE);
+ mServiceDescription = osuInfo.getServiceDescription(OSUManager.LOCALE);
+ mIconData = osuInfo.getIconFileElement().getIconData();
+ mId = osuInfo.getOsuID();
+ }
+
+ public String getName() {
+ return mName;
+ }
+
+ public String getServiceDescription() {
+ return mServiceDescription;
+ }
+
+ public byte[] getIconData() {
+ return mIconData;
+ }
+
+ public int getId() {
+ return mId;
+ }
+
+ private OSUData(Parcel in) {
+ mName = in.readString();
+ mServiceDescription = in.readString();
+ int iconSize = in.readInt();
+ mIconData = new byte[iconSize];
+ in.readByteArray(mIconData);
+ mId = in.readInt();
+ }
+
+ public static final Parcelable.Creator CREATOR = new Parcelable.Creator() {
+ public OSUData createFromParcel(Parcel in) {
+ return new OSUData(in);
+ }
+
+ public OSUData[] newArray(int size) {
+ return new OSUData[size];
+ }
+ };
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeString(mName);
+ dest.writeString(mServiceDescription);
+ dest.writeByteArray(mIconData);
+ dest.writeInt(mId);
+ }
+}
diff --git a/packages/Osu/src/com/android/hotspot2/app/OSUService.java b/packages/Osu/src/com/android/hotspot2/app/OSUService.java
new file mode 100644
index 0000000000000..62a203dbfde0b
--- /dev/null
+++ b/packages/Osu/src/com/android/hotspot2/app/OSUService.java
@@ -0,0 +1,202 @@
+package com.android.hotspot2.app;
+
+import android.app.IntentService;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.net.wifi.WifiConfiguration;
+import android.net.wifi.WifiInfo;
+import android.net.wifi.WifiManager;
+import android.os.Bundle;
+import android.os.IBinder;
+import android.util.Log;
+
+import com.android.anqp.OSUProvider;
+import com.android.hotspot2.PasspointMatch;
+import com.android.hotspot2.osu.OSUManager;
+
+import java.io.IOException;
+import java.util.List;
+
+/**
+ * This is the Hotspot 2.0 release 2 OSU background service that is continuously running and caches
+ * OSU information.
+ *
+ * The OSU App is made up of two services; FlowService and OSUService.
+ *
+ * OSUService is a long running light weight service, kept alive throughout the lifetime of the
+ * operating system by being bound from the framework (in WifiManager in stage
+ * PHASE_THIRD_PARTY_APPS_CAN_START), and is responsible for continuously caching OSU information
+ * and notifying the UI when OSUs are available.
+ *
+ * FlowService is only started on demand from OSUService and is responsible for handling actual
+ * provisioning and remediation flows, and requires a fairly significant memory footprint.
+ *
+ * FlowService is defined to run in its own process through the definition
+ *
+ * in the AndroidManifest.
+ * This is done as a means to keep total app memory footprint low (pss < 10M) and only start the
+ * FlowService on demand and make it available for "garbage collection" by the OS when not in use.
+ */
+public class OSUService extends IntentService {
+ public static final String REMEDIATION_DONE_ACTION = "com.android.hotspot2.REMEDIATION_DONE";
+ public static final String REMEDIATION_FQDN_EXTRA = "com.android.hotspot2.REMEDIATION_FQDN";
+ public static final String REMEDIATION_POLICY_EXTRA = "com.android.hotspot2.REMEDIATION_POLICY";
+
+ private static final String[] INTENTS = {
+ WifiManager.SCAN_RESULTS_AVAILABLE_ACTION,
+ WifiManager.PASSPOINT_WNM_FRAME_RECEIVED_ACTION,
+ WifiManager.PASSPOINT_ICON_RECEIVED_ACTION,
+ WifiManager.CONFIGURED_NETWORKS_CHANGED_ACTION,
+ WifiManager.WIFI_STATE_CHANGED_ACTION,
+ WifiManager.NETWORK_STATE_CHANGED_ACTION,
+ REMEDIATION_DONE_ACTION
+ };
+
+ private OSUManager mOsuManager;
+ private final LocalServiceBinder mLocalServiceBinder;
+
+ public OSUService() {
+ super("OSUService");
+ mLocalServiceBinder = new LocalServiceBinder(this);
+ }
+
+ /*
+ public final class OSUAccessorImpl extends IOSUAccessor.Stub {
+ public List getOsuData() {
+ List infos = getOsuInfos();
+ List data = new ArrayList<>(infos.size());
+ for (OSUInfo osuInfo : infos) {
+ data.add(new OSUData(osuInfo));
+ }
+ return data;
+ }
+
+ public void selectOsu(int id) {
+ OSUService.this.selectOsu(id);
+ }
+ }
+ */
+
+ @Override
+ public int onStartCommand(Intent intent, int flags, int startId) {
+ onHandleIntent(intent);
+ return START_STICKY;
+ }
+
+ @Override
+ public IBinder onBind(Intent intent) {
+ BroadcastReceiver receiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ handleIntent(intent.getAction(), intent);
+ }
+ };
+ for (String intentString : INTENTS) {
+ registerReceiver(receiver, new IntentFilter(intentString));
+ }
+ return mLocalServiceBinder;
+ }
+
+ @Override
+ protected void onHandleIntent(Intent intent) {
+ if (intent == null) {
+ Log.d(OSUManager.TAG, "Null intent!");
+ return;
+ }
+ //handleIntent(intent.getStringExtra(MainActivity.ACTION_KEY), intent);
+ }
+
+ private void handleIntent(String action, Intent intent) {
+ WifiManager wifiManager = (WifiManager) getSystemService(Context.WIFI_SERVICE);
+ Bundle bundle = intent.getExtras();
+ if (mOsuManager == null) {
+ mOsuManager = new OSUManager(this);
+ }
+ Log.d(OSUManager.TAG, "Got intent " + intent.getAction());
+
+ switch (action) {
+ case WifiManager.SCAN_RESULTS_AVAILABLE_ACTION:
+ mOsuManager.pushScanResults(wifiManager.getScanResults());
+ break;
+ case WifiManager.PASSPOINT_WNM_FRAME_RECEIVED_ACTION:
+ long bssid = bundle.getLong(WifiManager.EXTRA_PASSPOINT_WNM_BSSID);
+ String url = bundle.getString(WifiManager.EXTRA_PASSPOINT_WNM_URL);
+
+ try {
+ if (bundle.containsKey(WifiManager.EXTRA_PASSPOINT_WNM_METHOD)) {
+ int method = bundle.getInt(WifiManager.EXTRA_PASSPOINT_WNM_METHOD);
+ if (method != OSUProvider.OSUMethod.SoapXml.ordinal()) {
+ Log.w(OSUManager.TAG, "Unsupported remediation method: " + method);
+ return;
+ }
+ PasspointMatch match = null;
+ if (bundle.containsKey(WifiManager.EXTRA_PASSPOINT_WNM_PPOINT_MATCH)) {
+ int ordinal =
+ bundle.getInt(WifiManager.EXTRA_PASSPOINT_WNM_PPOINT_MATCH);
+ if (ordinal >= 0 && ordinal < PasspointMatch.values().length) {
+ match = PasspointMatch.values()[ordinal];
+ }
+ }
+ mOsuManager.wnmRemediate(bssid, url, match);
+ } else if (bundle.containsKey(WifiManager.EXTRA_PASSPOINT_WNM_ESS)) {
+ boolean ess = bundle.getBoolean(WifiManager.EXTRA_PASSPOINT_WNM_ESS);
+ int delay = bundle.getInt(WifiManager.EXTRA_PASSPOINT_WNM_DELAY);
+ mOsuManager.deauth(bssid, ess, delay, url);
+ } else {
+ Log.w(OSUManager.TAG, "Unknown WNM event");
+ }
+ } catch (IOException e) {
+ Log.w(OSUManager.TAG, "Remediation event failed to parse: " + e);
+ }
+ break;
+ case WifiManager.PASSPOINT_ICON_RECEIVED_ACTION:
+ mOsuManager.notifyIconReceived(
+ bundle.getLong(WifiManager.EXTRA_PASSPOINT_ICON_BSSID),
+ bundle.getString(WifiManager.EXTRA_PASSPOINT_ICON_FILE),
+ bundle.getByteArray(WifiManager.EXTRA_PASSPOINT_ICON_DATA));
+ break;
+ case WifiManager.NETWORK_STATE_CHANGED_ACTION:
+ mOsuManager.networkConnectChange(
+ (WifiInfo) intent.getParcelableExtra(WifiManager.EXTRA_WIFI_INFO));
+ break;
+ case WifiManager.CONFIGURED_NETWORKS_CHANGED_ACTION:
+ boolean multiNetwork =
+ bundle.getBoolean(WifiManager.EXTRA_MULTIPLE_NETWORKS_CHANGED, false);
+ if (multiNetwork) {
+ mOsuManager.networkConfigChanged();
+ } else if (bundle.getInt(WifiManager.EXTRA_CHANGE_REASON,
+ WifiManager.CHANGE_REASON_CONFIG_CHANGE)
+ == WifiManager.CHANGE_REASON_REMOVED) {
+ WifiConfiguration configuration =
+ intent.getParcelableExtra(WifiManager.EXTRA_WIFI_CONFIGURATION);
+ mOsuManager.networkDeleted(configuration);
+ } else {
+ mOsuManager.networkConfigChanged();
+ }
+ break;
+ case WifiManager.WIFI_STATE_CHANGED_ACTION:
+ int state = bundle.getInt(WifiManager.EXTRA_WIFI_STATE);
+ if (state == WifiManager.WIFI_STATE_DISABLED) {
+ mOsuManager.wifiStateChange(false);
+ } else if (state == WifiManager.WIFI_STATE_ENABLED) {
+ mOsuManager.wifiStateChange(true);
+ }
+ break;
+ case REMEDIATION_DONE_ACTION:
+ String fqdn = bundle.getString(REMEDIATION_FQDN_EXTRA);
+ boolean policy = bundle.getBoolean(REMEDIATION_POLICY_EXTRA);
+ mOsuManager.remediationDone(fqdn, policy);
+ break;
+ }
+ }
+
+ public List getOsuData() {
+ return mOsuManager.getAvailableOSUs();
+ }
+
+ public void selectOsu(int id) {
+ mOsuManager.setOSUSelection(id);
+ }
+}
diff --git a/packages/Osu/src/com/android/hotspot2/est/ESTHandler.java b/packages/Osu/src/com/android/hotspot2/est/ESTHandler.java
index a83eba4e2e904..cdcff80174975 100644
--- a/packages/Osu/src/com/android/hotspot2/est/ESTHandler.java
+++ b/packages/Osu/src/com/android/hotspot2/est/ESTHandler.java
@@ -14,7 +14,7 @@ import com.android.hotspot2.asn1.Asn1Object;
import com.android.hotspot2.asn1.Asn1Oid;
import com.android.hotspot2.asn1.OidMappings;
import com.android.hotspot2.osu.HTTPHandler;
-import com.android.hotspot2.osu.OSUManager;
+import com.android.hotspot2.osu.OSUFlowManager;
import com.android.hotspot2.osu.OSUSocketFactory;
import com.android.hotspot2.osu.commands.GetCertData;
import com.android.hotspot2.pps.HomeSP;
@@ -81,7 +81,7 @@ public class ESTHandler implements AutoCloseable {
private PrivateKey mClientKey;
public ESTHandler(GetCertData certData, Network network, OMADMAdapter omadmAdapter,
- KeyManager km, KeyStore ks, HomeSP homeSP, OSUManager.FlowType flowType)
+ KeyManager km, KeyStore ks, HomeSP homeSP, OSUFlowManager.FlowType flowType)
throws IOException, GeneralSecurityException {
mURL = new URL(certData.getServer());
mUser = certData.getUserName();
diff --git a/packages/Osu/src/com/android/hotspot2/flow/FlowService.java b/packages/Osu/src/com/android/hotspot2/flow/FlowService.java
new file mode 100644
index 0000000000000..8bbbf06344d56
--- /dev/null
+++ b/packages/Osu/src/com/android/hotspot2/flow/FlowService.java
@@ -0,0 +1,168 @@
+package com.android.hotspot2.flow;
+
+import android.annotation.Nullable;
+import android.app.IntentService;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.net.Network;
+import android.net.wifi.WifiInfo;
+import android.net.wifi.WifiManager;
+import android.os.IBinder;
+import android.util.Log;
+
+import com.android.hotspot2.osu.OSUFlowManager;
+import com.android.hotspot2.osu.OSUManager;
+import com.android.hotspot2.osu.OSUOperationStatus;
+import com.android.hotspot2.pps.HomeSP;
+
+import java.io.IOException;
+
+/**
+ * This is the Hotspot 2.0 release 2 service that handles actual provisioning and remediation flows.
+ *
+ * The OSU App is made up of two services; FlowService and OSUService.
+ *
+ * OSUService is a long running light weight service, kept alive throughout the lifetime of the
+ * operating system by being bound from the framework (in WifiManager in stage
+ * PHASE_THIRD_PARTY_APPS_CAN_START), and is responsible for continuously caching OSU information
+ * and notifying the UI when OSUs are available.
+ *
+ * FlowService is only started on demand from OSUService and is responsible for handling actual
+ * provisioning and remediation flows, and requires a fairly significant memory footprint.
+ *
+ * FlowService is defined to run in its own process through the definition
+ *
+ * in the AndroidManifest.
+ * This is done as a means to keep total app memory footprint low (pss < 10M) and only start the
+ * FlowService on demand and make it available for "garbage collection" by the OS when not in use.
+ */
+public class FlowService extends IntentService {
+ private static final String[] INTENTS = {
+ WifiManager.NETWORK_STATE_CHANGED_ACTION
+ };
+
+ private OSUFlowManager mOSUFlowManager;
+ private PlatformAdapter mPlatformAdapter;
+ private final FlowServiceImpl mOSUAccessor = new FlowServiceImpl();
+
+ /*
+ public FlowService(Context context) {
+ super("FlowService");
+ mOSUFlowManager = new OSUFlowManager();
+ mPlatformAdapter = new PlatformAdapter(context);
+ BroadcastReceiver receiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ handleIntent(intent.getAction(), intent);
+ }
+ };
+ for (String intentString : INTENTS) {
+ context.registerReceiver(receiver, new IntentFilter(intentString));
+ }
+ }
+ */
+
+ public FlowService() {
+ super("FlowService");
+ }
+
+ public final class FlowServiceImpl extends IFlowService.Stub {
+ public void provision(OSUInfo osuInfo) {
+ FlowService.this.provision(osuInfo);
+ }
+
+ public void remediate(String spFqdn, String url, boolean policy, Network network) {
+ FlowService.this.remediate(spFqdn, url, policy, network);
+ }
+
+ public void spDeleted(String fqdn) {
+ FlowService.this.serviceProviderDeleted(fqdn);
+ }
+ }
+
+ public void provision(OSUInfo osuInfo) {
+ try {
+ mOSUFlowManager.appendFlow(new OSUFlowManager.OSUFlow(osuInfo, mPlatformAdapter,
+ mPlatformAdapter.getKeyManager(null)));
+ } catch (IOException ioe) {
+ mPlatformAdapter.notifyUser(OSUOperationStatus.ProvisioningFailure, ioe.getMessage(),
+ osuInfo.getName(PlatformAdapter.LOCALE));
+ }
+ }
+
+ /**
+ * Initiate remediation
+ * @param spFqdn The FQDN of the current SP, not set for WNM based remediation
+ * @param url The URL of the remediation server
+ * @param policy Set if this is a policy update rather than a subscription update
+ * @param network The network to use for remediation
+ */
+ public void remediate(String spFqdn, String url, boolean policy, Network network) {
+ Log.d(OSUManager.TAG, "Starting remediation for " + spFqdn + " to " + url);
+ if (spFqdn != null) {
+ HomeSP homeSP = mPlatformAdapter.getHomeSP(spFqdn);
+ if (homeSP == null) {
+ Log.e(OSUManager.TAG, "No HomeSP object matches '" + spFqdn + "'");
+ return;
+ }
+
+ try {
+ mOSUFlowManager.appendFlow(new OSUFlowManager.OSUFlow(network, url,
+ mPlatformAdapter, mPlatformAdapter.getKeyManager(homeSP),
+ homeSP, policy
+ ? OSUFlowManager.FlowType.Policy : OSUFlowManager.FlowType.Remediation));
+ } catch (IOException ioe) {
+ Log.e(OSUManager.TAG, "Failed to remediate: " + ioe, ioe);
+ }
+ } else {
+ HomeSP homeSP = mPlatformAdapter.getCurrentSP();
+ if (homeSP == null) {
+ Log.e(OSUManager.TAG, "Remediation request on unidentified Passpoint network ");
+ return;
+ }
+
+ try {
+ mOSUFlowManager.appendFlow(new OSUFlowManager.OSUFlow(network, url,
+ mPlatformAdapter, mPlatformAdapter.getKeyManager(homeSP), homeSP,
+ OSUFlowManager.FlowType.Remediation));
+ } catch (IOException ioe) {
+ Log.e(OSUManager.TAG, "Failed to start remediation: " + ioe, ioe);
+ }
+ }
+ }
+
+ public void serviceProviderDeleted(String fqdn) {
+ mPlatformAdapter.serviceProviderDeleted(fqdn);
+ }
+
+ @Override
+ public IBinder onBind(Intent intent) {
+ mOSUFlowManager = new OSUFlowManager(this);
+ mPlatformAdapter = new PlatformAdapter(this);
+ BroadcastReceiver receiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ handleIntent(intent.getAction(), intent);
+ }
+ };
+ for (String intentString : INTENTS) {
+ registerReceiver(receiver, new IntentFilter(intentString));
+ }
+ return mOSUAccessor;
+ }
+
+ @Override
+ protected void onHandleIntent(@Nullable Intent intent) {
+ }
+
+ private void handleIntent(String action, Intent intent) {
+ if (WifiManager.NETWORK_STATE_CHANGED_ACTION.equals(action)) {
+ WifiInfo wifiInfo = intent.getParcelableExtra(WifiManager.EXTRA_WIFI_INFO);
+ if (wifiInfo != null) {
+ mOSUFlowManager.networkChange();
+ }
+ }
+ }
+}
diff --git a/packages/Osu/src/com/android/hotspot2/flow/IFlowService.aidl b/packages/Osu/src/com/android/hotspot2/flow/IFlowService.aidl
new file mode 100644
index 0000000000000..a61274fe88125
--- /dev/null
+++ b/packages/Osu/src/com/android/hotspot2/flow/IFlowService.aidl
@@ -0,0 +1,10 @@
+package com.android.hotspot2.flow;
+
+import com.android.hotspot2.flow.OSUInfo;
+import android.net.Network;
+
+interface IFlowService {
+ void provision(in OSUInfo osuInfo);
+ void remediate(String spFqdn, String url, boolean policy, in Network network);
+ void spDeleted(String fqdn);
+}
diff --git a/packages/Osu/src/com/android/hotspot2/flow/OSUInfo.aidl b/packages/Osu/src/com/android/hotspot2/flow/OSUInfo.aidl
new file mode 100644
index 0000000000000..172486e3f3bf3
--- /dev/null
+++ b/packages/Osu/src/com/android/hotspot2/flow/OSUInfo.aidl
@@ -0,0 +1,3 @@
+package com.android.hotspot2.flow;
+
+parcelable OSUInfo;
diff --git a/packages/Osu/src/com/android/hotspot2/osu/OSUInfo.java b/packages/Osu/src/com/android/hotspot2/flow/OSUInfo.java
similarity index 73%
rename from packages/Osu/src/com/android/hotspot2/osu/OSUInfo.java
rename to packages/Osu/src/com/android/hotspot2/flow/OSUInfo.java
index 1fa1dbd3ec523..401eccb96d050 100644
--- a/packages/Osu/src/com/android/hotspot2/osu/OSUInfo.java
+++ b/packages/Osu/src/com/android/hotspot2/flow/OSUInfo.java
@@ -1,6 +1,8 @@
-package com.android.hotspot2.osu;
+package com.android.hotspot2.flow;
import android.net.wifi.ScanResult;
+import android.os.Parcel;
+import android.os.Parcelable;
import android.util.Log;
import com.android.anqp.HSIconFileElement;
@@ -8,15 +10,18 @@ import com.android.anqp.I18Name;
import com.android.anqp.IconInfo;
import com.android.anqp.OSUProvider;
import com.android.hotspot2.Utils;
+import com.android.hotspot2.osu.OSUManager;
+import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
+import java.util.ListIterator;
import java.util.Locale;
import java.util.Set;
-public class OSUInfo {
+public class OSUInfo implements Parcelable {
public static final String GenericLocale = "zxx";
public enum IconStatus {
@@ -29,7 +34,6 @@ public class OSUInfo {
private final long mBSSID;
private final long mHESSID;
private final int mAnqpDomID;
- private final String mOsuSsid;
private final String mAdvertisingSSID;
private final OSUProvider mOSUProvider;
private final int mOsuID;
@@ -39,13 +43,12 @@ public class OSUInfo {
private String mIconFileName;
private IconInfo mIconInfo;
- public OSUInfo(ScanResult scanResult, String ssid, OSUProvider osuProvider, int osuID) {
+ public OSUInfo(ScanResult scanResult, OSUProvider osuProvider, int osuID) {
mOsuID = osuID;
mBSSID = Utils.parseMac(scanResult.BSSID);
mHESSID = scanResult.hessid;
mAnqpDomID = scanResult.anqpDomainId;
mAdvertisingSSID = scanResult.SSID;
- mOsuSsid = ssid;
mOSUProvider = osuProvider;
}
@@ -99,11 +102,11 @@ public class OSUInfo {
if (locale == null || name.getLocale().equals(locale)) {
return name.getText();
}
- scoreList.add(new ScoreEntry(name.getText(),
+ scoreList.add(new ScoreEntry<>(name.getText(),
languageScore(name.getLanguage(), locale)));
}
Collections.sort(scoreList);
- return scoreList.isEmpty() ? null : scoreList.iterator().next().getData();
+ return scoreList.isEmpty() ? null : scoreList.get(scoreList.size() - 1).getData();
}
public String getServiceDescription(Locale locale) {
@@ -116,7 +119,7 @@ public class OSUInfo {
languageScore(service.getLanguage(), locale)));
}
Collections.sort(scoreList);
- return scoreList.isEmpty() ? null : scoreList.iterator().next().getData();
+ return scoreList.isEmpty() ? null : scoreList.get(scoreList.size() - 1).getData();
}
public int getOsuID() {
@@ -182,6 +185,11 @@ public class OSUInfo {
public int compareTo(ScoreEntry other) {
return Integer.compare(mScore, other.mScore);
}
+
+ @Override
+ public String toString() {
+ return String.format("%d for '%s'", mScore, mData);
+ }
}
public List getIconInfo(Locale locale, Set types, int width, int height) {
@@ -219,8 +227,9 @@ public class OSUInfo {
}
Collections.sort(matches);
List icons = new ArrayList<>(matches.size());
- for (ScoreEntry scoredIcon : matches) {
- icons.add(scoredIcon.getData());
+ ListIterator> matchIterator = matches.listIterator(matches.size());
+ while (matchIterator.hasPrevious()) {
+ icons.add(matchIterator.previous().getData());
}
return icons;
}
@@ -243,7 +252,7 @@ public class OSUInfo {
}
public String getOsuSsid() {
- return mOsuSsid;
+ return mOSUProvider.getSSID();
}
public OSUProvider getOSUProvider() {
@@ -253,6 +262,60 @@ public class OSUInfo {
@Override
public String toString() {
return String.format("OSU Info '%s' %012x -> %s, icon %s",
- mOsuSsid, mBSSID, getServiceDescription(null), mIconStatus);
+ getOsuSsid(), mBSSID, getServiceDescription(null), mIconStatus);
+ }
+
+ private OSUInfo(Parcel in) {
+ mBSSID = in.readLong();
+ mHESSID = in.readLong();
+ mAnqpDomID = in.readInt();
+ mAdvertisingSSID = in.readString();
+ mOsuID = in.readInt();
+ mOSUBssid = in.readLong();
+ mIconFileName = in.readString();
+ mIconStatus = Utils.mapEnum(in.readInt(), IconStatus.class);
+ OSUProvider osuProvider;
+ try {
+ osuProvider = new OSUProvider(in);
+ } catch (IOException ioe) {
+ osuProvider = null;
+ }
+ mOSUProvider = osuProvider;
+ if (osuProvider == null) {
+ return;
+ }
+ mIconFileElement = new HSIconFileElement(in);
+ int iconIndex = in.readInt();
+ mIconInfo = iconIndex >= 0 && iconIndex < mOSUProvider.getIcons().size()
+ ? mOSUProvider.getIcons().get(iconIndex) : null;
+ }
+
+ public static final Parcelable.Creator CREATOR = new Parcelable.Creator() {
+ public OSUInfo createFromParcel(Parcel in) {
+ return new OSUInfo(in);
+ }
+
+ public OSUInfo[] newArray(int size) {
+ return new OSUInfo[size];
+ }
+ };
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeLong(mBSSID);
+ dest.writeLong(mHESSID);
+ dest.writeInt(mAnqpDomID);
+ dest.writeString(mAdvertisingSSID);
+ dest.writeInt(mOsuID);
+ dest.writeLong(mOSUBssid);
+ dest.writeString(mIconFileName);
+ dest.writeInt(mIconStatus.ordinal());
+ mOSUProvider.writeParcel(dest);
+ mIconFileElement.writeParcel(dest);
}
}
diff --git a/packages/Osu/src/com/android/hotspot2/flow/PlatformAdapter.java b/packages/Osu/src/com/android/hotspot2/flow/PlatformAdapter.java
new file mode 100644
index 0000000000000..0c901711cec3c
--- /dev/null
+++ b/packages/Osu/src/com/android/hotspot2/flow/PlatformAdapter.java
@@ -0,0 +1,611 @@
+package com.android.hotspot2.flow;
+
+import android.content.Context;
+import android.content.Intent;
+import android.net.Network;
+import android.net.wifi.PasspointManagementObjectDefinition;
+import android.net.wifi.WifiConfiguration;
+import android.net.wifi.WifiEnterpriseConfig;
+import android.net.wifi.WifiInfo;
+import android.net.wifi.WifiManager;
+import android.util.Log;
+
+import com.android.configparse.ConfigBuilder;
+import com.android.hotspot2.AppBridge;
+import com.android.hotspot2.Utils;
+import com.android.hotspot2.app.OSUService;
+import com.android.hotspot2.omadm.MOManager;
+import com.android.hotspot2.omadm.MOTree;
+import com.android.hotspot2.omadm.OMAConstants;
+import com.android.hotspot2.omadm.OMAException;
+import com.android.hotspot2.omadm.OMAParser;
+import com.android.hotspot2.osu.ClientKeyManager;
+import com.android.hotspot2.osu.OSUCertType;
+import com.android.hotspot2.osu.OSUManager;
+import com.android.hotspot2.osu.OSUOperationStatus;
+import com.android.hotspot2.osu.OSUSocketFactory;
+import com.android.hotspot2.osu.WiFiKeyManager;
+import com.android.hotspot2.osu.commands.MOData;
+import com.android.hotspot2.pps.HomeSP;
+
+import org.xml.sax.SAXException;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.security.GeneralSecurityException;
+import java.security.KeyStore;
+import java.security.KeyStoreException;
+import java.security.PrivateKey;
+import java.security.cert.Certificate;
+import java.security.cert.X509Certificate;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Enumeration;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+import java.util.Set;
+
+import javax.net.ssl.KeyManager;
+
+public class PlatformAdapter {
+ private static final String TAG = "OSUFLOW";
+
+ public static final Locale LOCALE = Locale.getDefault();
+
+ public static final String CERT_WFA_ALIAS = "wfa-root-";
+ public static final String CERT_REM_ALIAS = "rem-";
+ public static final String CERT_POLICY_ALIAS = "pol-";
+ public static final String CERT_SHARED_ALIAS = "shr-";
+ public static final String CERT_CLT_CERT_ALIAS = "clt-";
+ public static final String CERT_CLT_KEY_ALIAS = "prv-";
+ public static final String CERT_CLT_CA_ALIAS = "aaa-";
+
+ private static final String KEYSTORE_FILE = "passpoint.ks";
+
+ private final Context mContext;
+ private final File mKeyStoreFile;
+ private final KeyStore mKeyStore;
+ private final AppBridge mAppBridge;
+ private final Map mPasspointConfigs;
+
+ public PlatformAdapter(Context context) {
+ mContext = context;
+ mAppBridge = new AppBridge(context);
+
+ File appFolder = context.getFilesDir();
+ mKeyStoreFile = new File(appFolder, KEYSTORE_FILE);
+ Log.d(TAG, "KS file: " + mKeyStoreFile.getPath());
+ KeyStore ks = null;
+ try {
+ //ks = loadKeyStore(KEYSTORE_FILE, readCertsFromDisk(WFA_CA_LOC));
+ ks = loadKeyStore(mKeyStoreFile, OSUSocketFactory.buildCertSet());
+ } catch (IOException e) {
+ Log.e(TAG, "Failed to initialize Passpoint keystore, OSU disabled", e);
+ }
+ mKeyStore = ks;
+
+ mPasspointConfigs = loadAllSps(context);
+ }
+
+ private static KeyStore loadKeyStore(File ksFile, Set diskCerts)
+ throws IOException {
+ try {
+ KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType());
+ if (ksFile.exists()) {
+ try (FileInputStream in = new FileInputStream(ksFile)) {
+ keyStore.load(in, null);
+ }
+
+ // Note: comparing two sets of certs does not work.
+ boolean mismatch = false;
+ int loadCount = 0;
+ for (int n = 0; n < 1000; n++) {
+ String alias = String.format("%s%d", CERT_WFA_ALIAS, n);
+ Certificate cert = keyStore.getCertificate(alias);
+ if (cert == null) {
+ break;
+ }
+
+ loadCount++;
+ boolean matched = false;
+ Iterator iter = diskCerts.iterator();
+ while (iter.hasNext()) {
+ X509Certificate diskCert = iter.next();
+ if (cert.equals(diskCert)) {
+ iter.remove();
+ matched = true;
+ break;
+ }
+ }
+ if (!matched) {
+ mismatch = true;
+ break;
+ }
+ }
+ if (mismatch || !diskCerts.isEmpty()) {
+ Log.d(TAG, "Re-seeding Passpoint key store with " +
+ diskCerts.size() + " WFA certs");
+ for (int n = 0; n < 1000; n++) {
+ String alias = String.format("%s%d", CERT_WFA_ALIAS, n);
+ Certificate cert = keyStore.getCertificate(alias);
+ if (cert == null) {
+ break;
+ } else {
+ keyStore.deleteEntry(alias);
+ }
+ }
+ int index = 0;
+ for (X509Certificate caCert : diskCerts) {
+ keyStore.setCertificateEntry(
+ String.format("%s%d", CERT_WFA_ALIAS, index), caCert);
+ index++;
+ }
+
+ try (FileOutputStream out = new FileOutputStream(ksFile)) {
+ keyStore.store(out, null);
+ }
+ } else {
+ Log.d(TAG, "Loaded Passpoint key store with " + loadCount + " CA certs");
+ Enumeration aliases = keyStore.aliases();
+ while (aliases.hasMoreElements()) {
+ Log.d("ZXC", "KS Alias '" + aliases.nextElement() + "'");
+ }
+ }
+ } else {
+ keyStore.load(null, null);
+ int index = 0;
+ for (X509Certificate caCert : diskCerts) {
+ keyStore.setCertificateEntry(
+ String.format("%s%d", CERT_WFA_ALIAS, index), caCert);
+ index++;
+ }
+
+ try (FileOutputStream out = new FileOutputStream(ksFile)) {
+ keyStore.store(out, null);
+ }
+ Log.d(TAG, "Initialized Passpoint key store with " +
+ diskCerts.size() + " CA certs");
+ }
+ return keyStore;
+ } catch (GeneralSecurityException gse) {
+ throw new IOException(gse);
+ }
+ }
+
+ private static Map loadAllSps(Context context) {
+ Map passpointConfigs = new HashMap<>();
+
+ WifiManager wifiManager = (WifiManager) context.getSystemService(Context.WIFI_SERVICE);
+ List configs = wifiManager.getPrivilegedConfiguredNetworks();
+ if (configs == null) {
+ return passpointConfigs;
+ }
+ int count = 0;
+ for (WifiConfiguration config : configs) {
+ String moTree = config.getMoTree();
+ if (moTree != null) {
+ try {
+ passpointConfigs.put(config.FQDN, new PasspointConfig(config));
+ count++;
+ } catch (IOException | SAXException e) {
+ Log.w(OSUManager.TAG, "Failed to parse MO: " + e);
+ }
+ }
+ }
+ Log.d(OSUManager.TAG, "Loaded " + count + " SPs");
+ return passpointConfigs;
+ }
+
+ public KeyStore getKeyStore() {
+ return mKeyStore;
+ }
+
+ public Context getContext() {
+ return mContext;
+ }
+
+ /**
+ * Connect to an OSU provisioning network. The connection should not bring down other existing
+ * connection and the network should not be made the default network since the connection
+ * is solely for sign up and is neither intended for nor likely provides access to any
+ * generic resources.
+ *
+ * @param osuInfo The OSU info object that defines the parameters for the network. An OSU
+ * network is either an open network, or, if the OSU NAI is set, an "OSEN"
+ * network, which is an anonymous EAP-TLS network with special keys.
+ * @return an Integer holding the network-id of the just added network configuration, or null
+ * if the network existed prior to this call (was not added by the OSU infrastructure).
+ * The value will be used at the end of the OSU flow to delete the network as applicable.
+ * @throws IOException Issues:
+ * 1. The network id is not returned. addNetwork cannot be called from here since the method
+ * runs in the context of the app and doesn't have the appropriate permission.
+ * 2. The connection is not immediately usable if the network was not previously selected
+ * manually.
+ */
+ public Integer connect(OSUInfo osuInfo) throws IOException {
+ WifiManager wifiManager = (WifiManager) mContext.getSystemService(Context.WIFI_SERVICE);
+
+ WifiConfiguration config = new WifiConfiguration();
+ config.SSID = '"' + osuInfo.getOsuSsid() + '"';
+ if (osuInfo.getOSUBssid() != 0) {
+ config.BSSID = Utils.macToString(osuInfo.getOSUBssid());
+ Log.d(OSUManager.TAG, String.format("Setting BSSID of '%s' to %012x",
+ osuInfo.getOsuSsid(), osuInfo.getOSUBssid()));
+ }
+
+ if (osuInfo.getOSUProvider().getOsuNai() == null) {
+ config.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.NONE);
+ } else {
+ config.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.OSEN);
+ config.allowedProtocols.set(WifiConfiguration.Protocol.OSEN);
+ config.allowedPairwiseCiphers.set(WifiConfiguration.PairwiseCipher.CCMP);
+ config.allowedGroupCiphers.set(WifiConfiguration.GroupCipher.GTK_NOT_USED);
+ config.enterpriseConfig = new WifiEnterpriseConfig();
+ config.enterpriseConfig.setEapMethod(WifiEnterpriseConfig.Eap.UNAUTH_TLS);
+ config.enterpriseConfig.setIdentity(osuInfo.getOSUProvider().getOsuNai());
+ Set cas = OSUSocketFactory.buildCertSet();
+ config.enterpriseConfig.setCaCertificates(cas.toArray(new X509Certificate[cas.size()]));
+ }
+
+ int networkId = wifiManager.addNetwork(config);
+ if (networkId < 0) {
+ throw new IOException("Failed to add OSU network");
+ }
+ if (wifiManager.enableNetwork(networkId, true)) {
+ return networkId;
+ } else {
+ throw new IOException("Failed to enable OSU network");
+ }
+ }
+
+ /**
+ * @param homeSP The Home SP associated with the keying material in question. Passing
+ * null returns a "system wide" KeyManager to support pre-provisioned certs based
+ * on names retrieved from the ClientCertInfo request.
+ * @return A key manager suitable for the given configuration (or pre-provisioned keys).
+ */
+ public KeyManager getKeyManager(HomeSP homeSP) throws IOException {
+ return homeSP != null
+ ? new ClientKeyManager(homeSP, mKeyStore) : new WiFiKeyManager(mKeyStore);
+ }
+
+ public void provisioningComplete(OSUInfo osuInfo,
+ MOData moData, Map> certs,
+ PrivateKey privateKey, Network osuNetwork) {
+ try {
+ String xml = moData.getMOTree().toXml();
+ HomeSP homeSP = MOManager.buildSP(xml);
+
+ Integer spNwk = addNetwork(homeSP, certs, privateKey, osuNetwork);
+ if (spNwk == null) {
+ notifyUser(OSUOperationStatus.ProvisioningFailure,
+ "Failed to save network configuration", osuInfo.getName(LOCALE));
+ } else {
+ if (addSP(xml) < 0) {
+ deleteNetwork(spNwk);
+ Log.e(TAG, "Failed to provision: " + homeSP.getFQDN());
+ notifyUser(OSUOperationStatus.ProvisioningFailure, "Failed to add MO",
+ osuInfo.getName(LOCALE));
+ return;
+ }
+ Set rootCerts = OSUSocketFactory.getRootCerts(mKeyStore);
+ X509Certificate remCert = getCert(certs, OSUCertType.Remediation);
+ X509Certificate polCert = getCert(certs, OSUCertType.Policy);
+ int newCerts = 0;
+ if (privateKey != null) {
+ X509Certificate cltCert = getCert(certs, OSUCertType.Client);
+ mKeyStore.setKeyEntry(CERT_CLT_KEY_ALIAS + homeSP.getFQDN(),
+ privateKey, null, new X509Certificate[]{cltCert});
+ mKeyStore.setCertificateEntry(CERT_CLT_CERT_ALIAS + homeSP.getFQDN(), cltCert);
+ newCerts++;
+ }
+ boolean usingShared = false;
+ if (remCert != null) {
+ if (!rootCerts.contains(remCert)) {
+ if (remCert.equals(polCert)) {
+ mKeyStore.setCertificateEntry(CERT_SHARED_ALIAS + homeSP.getFQDN(),
+ remCert);
+ usingShared = true;
+ newCerts++;
+ } else {
+ mKeyStore.setCertificateEntry(CERT_REM_ALIAS + homeSP.getFQDN(),
+ remCert);
+ newCerts++;
+ }
+ }
+ }
+ if (!usingShared && polCert != null) {
+ if (!rootCerts.contains(polCert)) {
+ mKeyStore.setCertificateEntry(CERT_POLICY_ALIAS + homeSP.getFQDN(),
+ remCert);
+ newCerts++;
+ }
+ }
+
+
+ if (newCerts > 0) {
+ try (FileOutputStream out = new FileOutputStream(mKeyStoreFile)) {
+ mKeyStore.store(out, null);
+ }
+ }
+ notifyUser(OSUOperationStatus.ProvisioningSuccess, null, osuInfo.getName(LOCALE));
+ Log.d(TAG, "Provisioning complete.");
+ }
+ } catch (IOException | GeneralSecurityException | SAXException e) {
+ Log.e(TAG, "Failed to provision: " + e, e);
+ notifyUser(OSUOperationStatus.ProvisioningFailure, e.toString(),
+ osuInfo.getName(LOCALE));
+ }
+ }
+
+ public void remediationComplete(HomeSP homeSP, Collection mods,
+ Map> certs,
+ PrivateKey privateKey, boolean policy)
+ throws IOException, GeneralSecurityException {
+
+ HomeSP altSP = null;
+ if (modifySP(homeSP, mods) > 0) {
+ altSP = MOManager.modifySP(homeSP, getMOTree(homeSP), mods);
+ }
+
+ X509Certificate caCert = null;
+ List clientCerts = null;
+ if (certs != null) {
+ List certList = certs.get(OSUCertType.AAA);
+ caCert = certList != null && !certList.isEmpty() ? certList.iterator().next() : null;
+ clientCerts = certs.get(OSUCertType.Client);
+ }
+ if (altSP != null || certs != null) {
+ if (altSP == null) {
+ altSP = homeSP;
+ }
+ updateNetwork(altSP, caCert, clientCerts, privateKey);
+ }
+
+ Intent intent = new Intent(OSUService.REMEDIATION_DONE_ACTION);
+ intent.putExtra(OSUService.REMEDIATION_FQDN_EXTRA, homeSP.getFQDN());
+ intent.putExtra(OSUService.REMEDIATION_POLICY_EXTRA, policy);
+ mContext.sendBroadcast(intent);
+
+ notifyUser(OSUOperationStatus.ProvisioningSuccess, null, homeSP.getFriendlyName());
+ }
+
+ public void serviceProviderDeleted(String fqdn) {
+ int count = deleteCerts(mKeyStore, fqdn,
+ CERT_REM_ALIAS, CERT_POLICY_ALIAS, CERT_SHARED_ALIAS, CERT_CLT_CERT_ALIAS);
+
+ Log.d(TAG, "Passpoint network deleted, removing " + count + " key store entries");
+
+ try {
+ if (mKeyStore.getKey(CERT_CLT_KEY_ALIAS + fqdn, null) != null) {
+ mKeyStore.deleteEntry(CERT_CLT_KEY_ALIAS + fqdn);
+ }
+ } catch (GeneralSecurityException e) {
+ /**/
+ }
+
+ if (count > 0) {
+ try (FileOutputStream out = new FileOutputStream(mKeyStoreFile)) {
+ mKeyStore.store(out, null);
+ } catch (IOException | GeneralSecurityException e) {
+ Log.w(TAG, "Failed to remove certs from key store: " + e);
+ }
+ }
+ }
+
+ private static int deleteCerts(KeyStore keyStore, String fqdn, String... prefixes) {
+ int count = 0;
+ for (String prefix : prefixes) {
+ try {
+ String alias = prefix + fqdn;
+ Certificate cert = keyStore.getCertificate(alias);
+ if (cert != null) {
+ keyStore.deleteEntry(alias);
+ count++;
+ }
+ } catch (KeyStoreException kse) {
+ /**/
+ }
+ }
+ return count;
+ }
+
+ private static X509Certificate getCert(Map> certMap,
+ OSUCertType certType) {
+ List certs = certMap.get(certType);
+ if (certs == null || certs.isEmpty()) {
+ return null;
+ }
+ return certs.iterator().next();
+ }
+
+ public String notifyUser(OSUOperationStatus status, String message, String spName) {
+ if (status == OSUOperationStatus.UserInputComplete) {
+ return null;
+ }
+ mAppBridge.showStatus(status, spName, message, null);
+ return null;
+ }
+
+ public void provisioningFailed(String spName, String message) {
+ notifyUser(OSUOperationStatus.ProvisioningFailure, message, spName);
+ }
+
+ private Integer addNetwork(HomeSP homeSP, Map> certs,
+ PrivateKey privateKey, Network osuNetwork)
+ throws IOException, GeneralSecurityException {
+
+ List aaaTrust = certs.get(OSUCertType.AAA);
+ if (aaaTrust.isEmpty()) {
+ aaaTrust = certs.get(OSUCertType.CA); // Get the CAs from the EST flow.
+ }
+
+ WifiConfiguration config = ConfigBuilder.buildConfig(homeSP,
+ aaaTrust.iterator().next(),
+ certs.get(OSUCertType.Client), privateKey);
+
+ WifiManager wifiManager = (WifiManager) mContext.getSystemService(Context.WIFI_SERVICE);
+ int nwkId = wifiManager.addNetwork(config);
+ boolean saved = false;
+ if (nwkId >= 0) {
+ saved = wifiManager.saveConfiguration();
+ }
+ Log.d(OSUManager.TAG, "Wifi configuration " + nwkId +
+ " " + (saved ? "saved" : "not saved"));
+
+ if (saved) {
+ reconnect(osuNetwork, nwkId);
+ return nwkId;
+ } else {
+ return null;
+ }
+ }
+
+ private void updateNetwork(HomeSP homeSP, X509Certificate caCert,
+ List clientCerts, PrivateKey privateKey)
+ throws IOException, GeneralSecurityException {
+
+ WifiConfiguration config = getWifiConfig(homeSP);
+ if (config == null) {
+ throw new IOException("Failed to find matching network config");
+ }
+ Log.d(OSUManager.TAG, "Found matching config " + config.networkId + ", updating");
+
+ WifiEnterpriseConfig enterpriseConfig = config.enterpriseConfig;
+ WifiConfiguration newConfig = ConfigBuilder.buildConfig(homeSP,
+ caCert != null ? caCert : enterpriseConfig.getCaCertificate(),
+ clientCerts, privateKey);
+ newConfig.networkId = config.networkId;
+
+ WifiManager wifiManager = (WifiManager) mContext.getSystemService(Context.WIFI_SERVICE);
+ wifiManager.save(newConfig, null);
+ wifiManager.saveConfiguration();
+ }
+
+ private WifiConfiguration getWifiConfig(HomeSP homeSP) {
+ PasspointConfig passpointConfig = mPasspointConfigs.get(homeSP.getFQDN());
+ return passpointConfig != null ? passpointConfig.getWifiConfiguration() : null;
+ }
+
+ public MOTree getMOTree(HomeSP homeSP) {
+ PasspointConfig config = mPasspointConfigs.get(homeSP.getFQDN());
+ return config != null ? config.getmMOTree() : null;
+ }
+
+ public HomeSP getHomeSP(String fqdn) {
+ PasspointConfig passpointConfig = mPasspointConfigs.get(fqdn);
+ return passpointConfig != null ? passpointConfig.getHomeSP() : null;
+ }
+
+ public HomeSP getCurrentSP() {
+ PasspointConfig passpointConfig = getActivePasspointConfig();
+ return passpointConfig != null ? passpointConfig.getHomeSP() : null;
+ }
+
+ private PasspointConfig getActivePasspointConfig() {
+ WifiInfo wifiInfo = getConnectionInfo();
+ if (wifiInfo == null) {
+ return null;
+ }
+
+ for (PasspointConfig passpointConfig : mPasspointConfigs.values()) {
+ if (passpointConfig.getWifiConfiguration().networkId == wifiInfo.getNetworkId()) {
+ return passpointConfig;
+ }
+ }
+ return null;
+ }
+
+ private int addSP(String xml) throws IOException, SAXException {
+ WifiManager wifiManager = (WifiManager) mContext.getSystemService(Context.WIFI_SERVICE);
+ return wifiManager.addPasspointManagementObject(xml);
+ }
+
+ private int modifySP(HomeSP homeSP, Collection mods) throws IOException {
+ WifiManager wifiManager = (WifiManager) mContext.getSystemService(Context.WIFI_SERVICE);
+ List defMods = new ArrayList<>(mods.size());
+ for (MOData mod : mods) {
+ defMods.add(new PasspointManagementObjectDefinition(mod.getBaseURI(),
+ mod.getURN(), mod.getMOTree().toXml()));
+ }
+ return wifiManager.modifyPasspointManagementObject(homeSP.getFQDN(), defMods);
+ }
+
+ private void reconnect(Network osuNetwork, int newNwkId) {
+ WifiManager wifiManager = (WifiManager) mContext.getSystemService(Context.WIFI_SERVICE);
+ if (osuNetwork != null) {
+ wifiManager.disableNetwork(osuNetwork.netId);
+ }
+ if (newNwkId != WifiConfiguration.INVALID_NETWORK_ID) {
+ wifiManager.enableNetwork(newNwkId, true);
+ }
+ }
+
+ public void deleteNetwork(int id) {
+ WifiManager wifiManager = (WifiManager) mContext.getSystemService(Context.WIFI_SERVICE);
+ wifiManager.disableNetwork(id);
+ wifiManager.forget(id, null);
+ }
+
+ public WifiInfo getConnectionInfo() {
+ WifiManager wifiManager = (WifiManager) mContext.getSystemService(Context.WIFI_SERVICE);
+ return wifiManager.getConnectionInfo();
+ }
+
+ public Network getCurrentNetwork() {
+ WifiManager wifiManager = (WifiManager) mContext.getSystemService(Context.WIFI_SERVICE);
+ return wifiManager.getCurrentNetwork();
+ }
+
+ public WifiConfiguration getActiveWifiConfig() {
+ WifiInfo wifiInfo = getConnectionInfo();
+ if (wifiInfo == null) {
+ return null;
+ }
+ WifiManager wifiManager = (WifiManager) mContext.getSystemService(Context.WIFI_SERVICE);
+ List configs = wifiManager.getConfiguredNetworks();
+ if (configs == null) {
+ return null;
+ }
+ for (WifiConfiguration config : configs) {
+ if (config.networkId == wifiInfo.getNetworkId()) {
+ return config;
+ }
+ }
+ return null;
+ }
+
+ private static class PasspointConfig {
+ private final WifiConfiguration mWifiConfiguration;
+ private final MOTree mMOTree;
+ private final HomeSP mHomeSP;
+
+ private PasspointConfig(WifiConfiguration config) throws IOException, SAXException {
+ mWifiConfiguration = config;
+ OMAParser omaParser = new OMAParser();
+ mMOTree = omaParser.parse(config.getMoTree(), OMAConstants.PPS_URN);
+ List spList = MOManager.buildSPs(mMOTree);
+ if (spList.size() != 1) {
+ throw new OMAException("Expected exactly one HomeSP, got " + spList.size());
+ }
+ mHomeSP = spList.iterator().next();
+ }
+
+ public WifiConfiguration getWifiConfiguration() {
+ return mWifiConfiguration;
+ }
+
+ public HomeSP getHomeSP() {
+ return mHomeSP;
+ }
+
+ public MOTree getmMOTree() {
+ return mMOTree;
+ }
+ }
+}
diff --git a/packages/Osu/src/com/android/hotspot2/osu/ClientKeyManager.java b/packages/Osu/src/com/android/hotspot2/osu/ClientKeyManager.java
index 47b2f9347b282..cfc84bbc62259 100644
--- a/packages/Osu/src/com/android/hotspot2/osu/ClientKeyManager.java
+++ b/packages/Osu/src/com/android/hotspot2/osu/ClientKeyManager.java
@@ -2,6 +2,7 @@ package com.android.hotspot2.osu;
import android.util.Log;
+import com.android.hotspot2.flow.PlatformAdapter;
import com.android.hotspot2.pps.HomeSP;
import java.io.IOException;
@@ -30,9 +31,9 @@ public class ClientKeyManager implements X509KeyManager {
public ClientKeyManager(HomeSP homeSP, KeyStore keyStore) throws IOException {
mKeyStore = keyStore;
mAliasMap = new HashMap<>();
- mAliasMap.put(OSUCertType.AAA, OSUManager.CERT_CLT_CA_ALIAS + homeSP.getFQDN());
- mAliasMap.put(OSUCertType.Client, OSUManager.CERT_CLT_CERT_ALIAS + homeSP.getFQDN());
- mAliasMap.put(OSUCertType.PrivateKey, OSUManager.CERT_CLT_KEY_ALIAS + homeSP.getFQDN());
+ mAliasMap.put(OSUCertType.AAA, PlatformAdapter.CERT_CLT_CA_ALIAS + homeSP.getFQDN());
+ mAliasMap.put(OSUCertType.Client, PlatformAdapter.CERT_CLT_CERT_ALIAS + homeSP.getFQDN());
+ mAliasMap.put(OSUCertType.PrivateKey, PlatformAdapter.CERT_CLT_KEY_ALIAS + homeSP.getFQDN());
mTempKeys = new HashMap<>();
}
diff --git a/packages/Osu/src/com/android/hotspot2/osu/HTTPHandler.java b/packages/Osu/src/com/android/hotspot2/osu/HTTPHandler.java
index 1a66fcf198c55..4b583df1ce443 100644
--- a/packages/Osu/src/com/android/hotspot2/osu/HTTPHandler.java
+++ b/packages/Osu/src/com/android/hotspot2/osu/HTTPHandler.java
@@ -171,8 +171,10 @@ public class HTTPHandler implements AutoCloseable {
}
public void close() throws IOException {
+ mSocket.shutdownInput();
+ mSocket.shutdownOutput();
+ mSocket.close();
mIn.close();
mOut.close();
- mSocket.close();
}
}
diff --git a/packages/Osu/src/com/android/hotspot2/osu/IconCache.java b/packages/Osu/src/com/android/hotspot2/osu/IconCache.java
index 2d82dd86caea5..bd8a018a5f0be 100644
--- a/packages/Osu/src/com/android/hotspot2/osu/IconCache.java
+++ b/packages/Osu/src/com/android/hotspot2/osu/IconCache.java
@@ -5,6 +5,7 @@ import android.util.Log;
import com.android.anqp.HSIconFileElement;
import com.android.anqp.IconInfo;
import com.android.hotspot2.Utils;
+import com.android.hotspot2.flow.OSUInfo;
import java.net.ProtocolException;
import java.nio.BufferUnderflowException;
@@ -132,7 +133,7 @@ public class IconCache extends Thread {
if (!mBssids.contains(bssid)) {
return 0;
}
- Log.d("ZXZ", "Updating icon on " + mQueued.size() + " osus");
+ Log.d(OSUManager.TAG, "Updating icon on " + mQueued.size() + " osus");
for (OSUInfo osuInfo : mQueued) {
osuInfo.setIconFileElement(iconFileElement, mFileName);
}
@@ -188,29 +189,28 @@ public class IconCache extends Thread {
HSIconFileElement iconFileElement = get(key, fileName);
if (iconFileElement != null) {
osuInfo.setIconFileElement(iconFileElement, fileName);
- Log.d("ZXZ", "Icon cache hit for " + osuInfo + "/" + fileName);
+ Log.d(OSUManager.TAG, "Icon cache hit for " + osuInfo + "/" + fileName);
modCount++;
} else {
FileEntry fileEntry = enqueue(key, fileName, osuInfo);
if (fileEntry != null) {
- Log.d("ZXZ", "Initiating icon query for " + osuInfo + "/" + fileName);
+ Log.d(OSUManager.TAG, "Initiating icon query for "
+ + osuInfo + "/" + fileName);
mOsuManager.doIconQuery(osuInfo.getBSSID(), fileName);
} else {
- Log.d("ZXZ", "Piggybacking icon query for " + osuInfo + "/" + fileName);
+ Log.d(OSUManager.TAG, "Piggybacking icon query for "
+ + osuInfo + "/" + fileName);
}
}
}
}
- Log.d("ZXZ", "Current keys " + current + " from " + osuInfos);
-
// Drop all non-current ESS's
Iterator pendingKeys = mPending.keySet().iterator();
while (pendingKeys.hasNext()) {
EssKey key = pendingKeys.next();
if (!current.contains(key)) {
pendingKeys.remove();
- Log.d("ZXZ", "Removing pending " + key);
}
}
Iterator cacheKeys = mCache.keySet().iterator();
@@ -218,7 +218,6 @@ public class IconCache extends Thread {
EssKey key = cacheKeys.next();
if (!current.contains(key)) {
cacheKeys.remove();
- Log.d("ZXZ", "Removing cache " + key);
}
}
return modCount;
@@ -235,7 +234,7 @@ public class IconCache extends Thread {
}
public int notifyIconReceived(long bssid, String fileName, byte[] iconData) {
- Log.d("ZXZ", String.format("Icon '%s':%d received from %012x",
+ Log.d(OSUManager.TAG, String.format("Icon '%s':%d received from %012x",
fileName, iconData != null ? iconData.length : -1, bssid));
if (fileName == null || iconData == null) {
return 0;
@@ -273,7 +272,6 @@ public class IconCache extends Thread {
}
public void tick(boolean wifiOff) {
- Log.d("ZXZ", "Ticking icon cache: " + wifiOff);
if (wifiOff) {
mPending.clear();
mCache.clear();
@@ -291,10 +289,8 @@ public class IconCache extends Thread {
FileEntry fileEntry = fileEntries.next().getValue();
long age = now - fileEntry.getTimestamp();
if (age > REQUERY_TIMEOUT || fileEntry.getAndIncrementRetry() > MAX_RETRY) {
- Log.d("ZXZ", "Timing out " + fileEntry);
fileEntries.remove();
} else if (age > REQUERY_TIME) {
- Log.d("ZXZ", "Retrying " + fileEntry);
mOsuManager.doIconQuery(fileEntry.getLastBssid(), fileEntry.getFileName());
}
}
diff --git a/packages/Osu/src/com/android/hotspot2/osu/OSUCache.java b/packages/Osu/src/com/android/hotspot2/osu/OSUCache.java
index dadda26dbf0de..260fb723ab369 100644
--- a/packages/Osu/src/com/android/hotspot2/osu/OSUCache.java
+++ b/packages/Osu/src/com/android/hotspot2/osu/OSUCache.java
@@ -81,6 +81,8 @@ public class OSUCache {
for (ScanResult scanResult : scanResults) {
AnqpInformationElement[] osuInfo = scanResult.anqpElements;
if (osuInfo != null && osuInfo.length > 0) {
+ Log.d(OSUManager.TAG, scanResult.SSID +
+ " has " + osuInfo.length + " ANQP elements");
putResult(scanResult, osuInfo);
}
}
@@ -89,6 +91,8 @@ public class OSUCache {
private void putResult(ScanResult scanResult, AnqpInformationElement[] elements) {
for (AnqpInformationElement ie : elements) {
+ Log.d(OSUManager.TAG, String.format("ANQP IE %d vid %x size %d", ie.getElementId(),
+ ie.getVendorId(), ie.getPayload().length));
if (ie.getElementId() == AnqpInformationElement.HS_OSU_PROVIDERS
&& ie.getVendorId() == AnqpInformationElement.HOTSPOT20_VENDOR_ID) {
try {
@@ -106,6 +110,7 @@ public class OSUCache {
}
private void putProviders(ScanResult scanResult, HSOsuProvidersElement osuProviders) {
+ Log.d(OSUManager.TAG, osuProviders.getProviders().size() + " OSU providers in element");
for (OSUProvider provider : osuProviders.getProviders()) {
// Make a predictive put
ScanResult existing = mBatchedOSUs.put(provider, scanResult);
@@ -126,13 +131,14 @@ public class OSUCache {
mCache.put(entry.getKey(), new ScanInstance(entry.getValue(), mInstant));
changes++;
if (current == null) {
- Log.d("ZXZ", "Add OSU " + entry.getKey() + " from " + entry.getValue().SSID);
+ Log.d(OSUManager.TAG,
+ "Add OSU " + entry.getKey() + " from " + entry.getValue().SSID);
} else {
- Log.d("ZXZ", "Update OSU " + entry.getKey() + " with " +
+ Log.d(OSUManager.TAG, "Update OSU " + entry.getKey() + " with " +
entry.getValue().SSID + " to " + current);
}
} else {
- Log.d("ZXZ", "Existing OSU " + entry.getKey() + ", "
+ Log.d(OSUManager.TAG, "Existing OSU " + entry.getKey() + ", "
+ current.getInstant() + " -> " + mInstant);
current.updateInstant(mInstant);
}
@@ -140,7 +146,7 @@ public class OSUCache {
for (Map.Entry entry : aged.entrySet()) {
if (mInstant - entry.getValue().getInstant() > SCAN_BATCH_HISTORY_SIZE) {
- Log.d("ZXZ", "Remove OSU " + entry.getKey() + ", "
+ Log.d(OSUManager.TAG, "Remove OSU " + entry.getKey() + ", "
+ entry.getValue().getInstant() + " @ " + mInstant);
mCache.remove(entry.getKey());
changes++;
diff --git a/packages/Osu/src/com/android/hotspot2/osu/OSUClient.java b/packages/Osu/src/com/android/hotspot2/osu/OSUClient.java
index d842dbf405885..8179a631dd290 100644
--- a/packages/Osu/src/com/android/hotspot2/osu/OSUClient.java
+++ b/packages/Osu/src/com/android/hotspot2/osu/OSUClient.java
@@ -11,17 +11,22 @@ package com.android.hotspot2.osu;
* subscription-server.r2-testbed-rks IN A 10.123.107.107
*/
+import android.content.Context;
+import android.content.Intent;
import android.net.Network;
import android.util.Log;
import com.android.hotspot2.OMADMAdapter;
import com.android.hotspot2.est.ESTHandler;
+import com.android.hotspot2.flow.OSUInfo;
+import com.android.hotspot2.flow.PlatformAdapter;
import com.android.hotspot2.omadm.OMAConstants;
import com.android.hotspot2.omadm.OMANode;
import com.android.hotspot2.osu.commands.BrowserURI;
import com.android.hotspot2.osu.commands.ClientCertInfo;
import com.android.hotspot2.osu.commands.GetCertData;
import com.android.hotspot2.osu.commands.MOData;
+import com.android.hotspot2.osu.service.RedirectListener;
import com.android.hotspot2.pps.Credential;
import com.android.hotspot2.pps.HomeSP;
import com.android.hotspot2.pps.UpdateInfo;
@@ -38,6 +43,7 @@ import java.security.cert.X509Certificate;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
+import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
@@ -48,38 +54,46 @@ import javax.net.ssl.KeyManager;
public class OSUClient {
private static final String TAG = "OSUCLT";
- private static final String TTLS_OSU =
- "https://osu-server.r2-testbed-rks.wi-fi.org:9447/OnlineSignup/services/newUser/digest";
- private static final String TLS_OSU =
- "https://osu-server.r2-testbed-rks.wi-fi.org:9446/OnlineSignup/services/newUser/certificate";
private final OSUInfo mOSUInfo;
private final URL mURL;
private final KeyStore mKeyStore;
+ private final Context mContext;
+ private volatile HTTPHandler mHTTPHandler;
+ private volatile RedirectListener mRedirectListener;
- public OSUClient(OSUInfo osuInfo, KeyStore ks) throws MalformedURLException {
+ public OSUClient(OSUInfo osuInfo, KeyStore ks, Context context) throws MalformedURLException {
mOSUInfo = osuInfo;
mURL = new URL(osuInfo.getOSUProvider().getOSUServer());
mKeyStore = ks;
+ mContext = context;
}
- public OSUClient(String osu, KeyStore ks) throws MalformedURLException {
+ public OSUClient(String osu, KeyStore ks, Context context) throws MalformedURLException {
mOSUInfo = null;
mURL = new URL(osu);
mKeyStore = ks;
+ mContext = context;
}
- public void provision(OSUManager osuManager, Network network, KeyManager km)
+ public OSUInfo getOSUInfo() {
+ return mOSUInfo;
+ }
+
+ public void provision(PlatformAdapter platformAdapter, Network network, KeyManager km)
throws IOException, GeneralSecurityException {
try (HTTPHandler httpHandler = new HTTPHandler(StandardCharsets.UTF_8,
- OSUSocketFactory.getSocketFactory(mKeyStore, null, OSUManager.FlowType.Provisioning,
- network, mURL, km, true))) {
+ OSUSocketFactory.getSocketFactory(mKeyStore, null,
+ OSUFlowManager.FlowType.Provisioning, network, mURL, km, true))) {
+
+ mHTTPHandler = httpHandler;
SPVerifier spVerifier = new SPVerifier(mOSUInfo);
spVerifier.verify(httpHandler.getOSUCertificate(mURL));
- URL redirectURL = osuManager.prepareUserInput(mOSUInfo.getName(Locale.getDefault()));
- OMADMAdapter omadmAdapter = osuManager.getOMADMAdapter();
+ URL redirectURL = prepareUserInput(platformAdapter,
+ mOSUInfo.getName(Locale.getDefault()));
+ OMADMAdapter omadmAdapter = getOMADMAdapter();
String regRequest = SOAPBuilder.buildPostDevDataResponse(RequestReason.SubRegistration,
null,
@@ -135,7 +149,7 @@ public class OSUClient {
throw new IOException("Bad or missing session ID in webURL");
}
- if (!osuManager.startUserInput(new URL(webURL), network)) {
+ if (!startUserInput(new URL(webURL), network)) {
throw new IOException("User session failed");
}
@@ -159,8 +173,8 @@ public class OSUClient {
moData = (MOData) provResponse.getCommandData();
} else {
try (ESTHandler estHandler = new ESTHandler((GetCertData) provResponse.
- getCommandData(), network, osuManager.getOMADMAdapter(),
- km, mKeyStore, null, OSUManager.FlowType.Provisioning)) {
+ getCommandData(), network, getOMADMAdapter(),
+ km, mKeyStore, null, OSUFlowManager.FlowType.Provisioning)) {
estHandler.execute(false);
certs.put(OSUCertType.CA, estHandler.getCACerts());
certs.put(OSUCertType.Client, estHandler.getClientCerts());
@@ -197,16 +211,19 @@ public class OSUClient {
}
retrieveCerts(moData.getMOTree().getRoot(), certs, network, km, mKeyStore);
- osuManager.provisioningComplete(mOSUInfo, moData, certs, clientKey, network);
+ platformAdapter.provisioningComplete(mOSUInfo, moData, certs, clientKey, network);
}
}
- public void remediate(OSUManager osuManager, Network network, KeyManager km, HomeSP homeSP,
- OSUManager.FlowType flowType)
+ public void remediate(PlatformAdapter platformAdapter, Network network, KeyManager km,
+ HomeSP homeSP, OSUFlowManager.FlowType flowType)
throws IOException, GeneralSecurityException {
try (HTTPHandler httpHandler = createHandler(network, homeSP, km, flowType)) {
- URL redirectURL = osuManager.prepareUserInput(homeSP.getFriendlyName());
- OMADMAdapter omadmAdapter = osuManager.getOMADMAdapter();
+
+ mHTTPHandler = httpHandler;
+
+ URL redirectURL = prepareUserInput(platformAdapter, homeSP.getFriendlyName());
+ OMADMAdapter omadmAdapter = getOMADMAdapter();
String regRequest = SOAPBuilder.buildPostDevDataResponse(RequestReason.SubRemediation,
null,
@@ -233,7 +250,7 @@ public class OSUClient {
redirectURL.toString(),
omadmAdapter.getMO(OMAConstants.DevInfoURN),
omadmAdapter.getMO(OMAConstants.DevDetailURN),
- osuManager.getMOTree(homeSP));
+ platformAdapter.getMOTree(homeSP));
Log.d(TAG, "Upload MO: " + ulMessage);
@@ -245,7 +262,7 @@ public class OSUClient {
}
if (pddResponse.getExecCommand() == ExecCommand.Browser) {
- if (flowType == OSUManager.FlowType.Policy) {
+ if (flowType == OSUFlowManager.FlowType.Policy) {
throw new IOException("Browser launch requested in policy flow");
}
String webURL = ((BrowserURI) pddResponse.getCommandData()).getURI();
@@ -256,7 +273,7 @@ public class OSUClient {
throw new IOException("Bad or missing session ID in webURL");
}
- if (!osuManager.startUserInput(new URL(webURL), network)) {
+ if (!startUserInput(new URL(webURL), network)) {
throw new IOException("User session failed");
}
@@ -275,7 +292,7 @@ public class OSUClient {
} else if (pddResponse.getExecCommand() == ExecCommand.GetCert) {
certs = new HashMap<>();
try (ESTHandler estHandler = new ESTHandler((GetCertData) pddResponse.
- getCommandData(), network, osuManager.getOMADMAdapter(),
+ getCommandData(), network, getOMADMAdapter(),
km, mKeyStore, homeSP, flowType)) {
estHandler.execute(true);
certs.put(OSUCertType.CA, estHandler.getCACerts());
@@ -347,17 +364,54 @@ public class OSUClient {
// There's a chicken and egg here: If the config is saved before sending update complete
// the network is lost and the remediation flow fails.
try {
- osuManager.remediationComplete(homeSP, mods, certs, clientKey);
+ platformAdapter.remediationComplete(homeSP, mods, certs, clientKey,
+ flowType == OSUFlowManager.FlowType.Policy);
} catch (IOException | GeneralSecurityException e) {
- osuManager.provisioningFailed(homeSP.getFriendlyName(), e.getMessage(), homeSP,
- OSUManager.FlowType.Remediation);
+ platformAdapter.provisioningFailed(homeSP.getFriendlyName(), e.getMessage());
error = OSUError.CommandFailed;
}
}
}
+ private OMADMAdapter getOMADMAdapter() {
+ return OMADMAdapter.getInstance(mContext);
+ }
+
+ private URL prepareUserInput(PlatformAdapter platformAdapter, String spName)
+ throws IOException {
+ mRedirectListener = new RedirectListener(platformAdapter, spName);
+ return mRedirectListener.getURL();
+ }
+
+ private boolean startUserInput(URL target, Network network)
+ throws IOException {
+ mRedirectListener.startService();
+
+ Intent intent = new Intent(mContext, OSUWebView.class);
+ intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ intent.putExtra(OSUWebView.OSU_NETWORK, network);
+ intent.putExtra(OSUWebView.OSU_URL, target.toString());
+ mContext.startActivity(intent);
+
+ return mRedirectListener.waitForUser();
+ }
+
+ public void close(boolean abort) {
+ if (mRedirectListener != null) {
+ mRedirectListener.abort();
+ mRedirectListener = null;
+ }
+ if (abort) {
+ try {
+ mHTTPHandler.close();
+ } catch (IOException ioe) {
+ /**/
+ }
+ }
+ }
+
private HTTPHandler createHandler(Network network, HomeSP homeSP, KeyManager km,
- OSUManager.FlowType flowType)
+ OSUFlowManager.FlowType flowType)
throws GeneralSecurityException, IOException {
Credential credential = homeSP.getCredential();
@@ -367,7 +421,7 @@ public class OSUClient {
String user;
byte[] password;
UpdateInfo subscriptionUpdate;
- if (flowType == OSUManager.FlowType.Policy) {
+ if (flowType == OSUFlowManager.FlowType.Policy) {
subscriptionUpdate = homeSP.getPolicy() != null ?
homeSP.getPolicy().getPolicyUpdate() : null;
} else {
@@ -439,8 +493,8 @@ public class OSUClient {
for (String urlString : urls) {
URL url = new URL(urlString);
HTTPHandler httpHandler = new HTTPHandler(StandardCharsets.UTF_8,
- OSUSocketFactory.getSocketFactory(ks, null, OSUManager.FlowType.Provisioning,
- network, url, km, false));
+ OSUSocketFactory.getSocketFactory(ks, null,
+ OSUFlowManager.FlowType.Provisioning, network, url, km, false));
certs.add((X509Certificate) certFactory.generateCertificate(httpHandler.doGet(url)));
}
@@ -457,7 +511,7 @@ public class OSUClient {
case "?":
for (OMANode node : root.getChildren()) {
if (!node.isLeaf()) {
- nodes = Arrays.asList(node);
+ nodes = Collections.singletonList(node);
break;
}
}
@@ -466,7 +520,7 @@ public class OSUClient {
nodes = root.getChildren();
break;
default:
- nodes = Arrays.asList(root.getChild(name));
+ nodes = Collections.singletonList(root.getChild(name));
break;
}
diff --git a/packages/Osu/src/com/android/hotspot2/osu/OSUFlowManager.java b/packages/Osu/src/com/android/hotspot2/osu/OSUFlowManager.java
new file mode 100644
index 0000000000000..0123d69881df7
--- /dev/null
+++ b/packages/Osu/src/com/android/hotspot2/osu/OSUFlowManager.java
@@ -0,0 +1,392 @@
+package com.android.hotspot2.osu;
+
+import android.content.Context;
+import android.content.Intent;
+import android.net.Network;
+import android.net.wifi.WifiConfiguration;
+import android.net.wifi.WifiInfo;
+import android.os.SystemClock;
+import android.util.Log;
+
+import com.android.hotspot2.Utils;
+import com.android.hotspot2.flow.FlowService;
+import com.android.hotspot2.flow.OSUInfo;
+import com.android.hotspot2.flow.PlatformAdapter;
+import com.android.hotspot2.pps.HomeSP;
+
+import java.io.IOException;
+import java.net.MalformedURLException;
+import java.util.Iterator;
+import java.util.LinkedList;
+
+import javax.net.ssl.KeyManager;
+
+public class OSUFlowManager {
+ private static final boolean MATCH_BSSID = false;
+ private static final long WAIT_QUANTA = 10000L;
+ private static final long WAIT_TIMEOUT = 1800000L;
+
+ private final Context mContext;
+ private final LinkedList mQueue;
+ private FlowWorker mWorker;
+ private OSUFlow mCurrent;
+
+ public OSUFlowManager(Context context) {
+ mContext = context;
+ mQueue = new LinkedList<>();
+ }
+
+ public enum FlowType {Provisioning, Remediation, Policy}
+
+ public static class OSUFlow implements Runnable {
+ private final OSUClient mOSUClient;
+ private final PlatformAdapter mPlatformAdapter;
+ private final HomeSP mHomeSP;
+ private final String mSpName;
+ private final FlowType mFlowType;
+ private final KeyManager mKeyManager;
+ private final Object mNetworkLock = new Object();
+ private final Network mNetwork;
+ private Network mResultNetwork;
+ private boolean mNetworkCreated;
+ private int mWifiNetworkId;
+ private volatile long mLaunchTime;
+ private volatile boolean mAborted;
+
+ /**
+ * A policy flow.
+ * @param osuInfo The OSU information for the flow (SSID, BSSID, URL)
+ * @param platformAdapter the platform adapter
+ * @param km A key manager for TLS
+ * @throws MalformedURLException
+ */
+ public OSUFlow(OSUInfo osuInfo, PlatformAdapter platformAdapter, KeyManager km)
+ throws MalformedURLException {
+
+ mWifiNetworkId = -1;
+ mNetwork = null;
+ mOSUClient = new OSUClient(osuInfo,
+ platformAdapter.getKeyStore(), platformAdapter.getContext());
+ mPlatformAdapter = platformAdapter;
+ mHomeSP = null;
+ mSpName = osuInfo.getName(OSUManager.LOCALE);
+ mFlowType = FlowType.Provisioning;
+ mKeyManager = km;
+ }
+
+ /**
+ * A Remediation flow for credential or policy provisioning.
+ * @param network The network to use, only set for timed provisioning
+ * @param osuURL The URL to connect to.
+ * @param platformAdapter the platform adapter
+ * @param km A key manager for TLS
+ * @param homeSP The Home SP to which this remediation flow pertains.
+ * @param flowType Remediation or Policy
+ * @throws MalformedURLException
+ */
+ public OSUFlow(Network network, String osuURL,
+ PlatformAdapter platformAdapter, KeyManager km, HomeSP homeSP,
+ FlowType flowType) throws MalformedURLException {
+
+ mNetwork = network;
+ mWifiNetworkId = network.netId;
+ mOSUClient = new OSUClient(osuURL,
+ platformAdapter.getKeyStore(), platformAdapter.getContext());
+ mPlatformAdapter = platformAdapter;
+ mHomeSP = homeSP;
+ mSpName = homeSP.getFriendlyName();
+ mFlowType = flowType;
+ mKeyManager = km;
+ }
+
+ private boolean deleteNetwork(OSUFlow next) {
+ synchronized (mNetworkLock) {
+ if (!mNetworkCreated) {
+ return false;
+ } else if (next.getFlowType() != FlowType.Provisioning) {
+ return true;
+ }
+ OSUInfo thisInfo = mOSUClient.getOSUInfo();
+ OSUInfo thatInfo = next.mOSUClient.getOSUInfo();
+ if (thisInfo.getOsuSsid().equals(thatInfo.getOsuSsid())
+ && thisInfo.getOSUBssid() == thatInfo.getOSUBssid()) {
+ // Reuse the OSU network from previous and carry forward the creation fact.
+ mNetworkCreated = true;
+ return false;
+ } else {
+ return true;
+ }
+ }
+ }
+
+ private Network connect() throws IOException {
+ Network network = networkMatch();
+
+ synchronized (mNetworkLock) {
+ mResultNetwork = network;
+ if (mResultNetwork != null) {
+ return mResultNetwork;
+ }
+ }
+
+ Log.d(OSUManager.TAG, "No network match for " + toString());
+
+ int osuNetworkId = -1;
+ boolean created = false;
+
+ if (mFlowType == FlowType.Provisioning) {
+ osuNetworkId = mPlatformAdapter.connect(mOSUClient.getOSUInfo());
+ created = true;
+ }
+
+ synchronized (mNetworkLock) {
+ mNetworkCreated = created;
+ if (created) {
+ mWifiNetworkId = osuNetworkId;
+ }
+ Log.d(OSUManager.TAG, String.format("%s waiting for %snet ID %d",
+ toString(), created ? "created " : "existing ", osuNetworkId));
+
+ while (mResultNetwork == null && !mAborted) {
+ try {
+ mNetworkLock.wait();
+ } catch (InterruptedException ie) {
+ throw new IOException("Interrupted");
+ }
+ }
+ if (mAborted) {
+ throw new IOException("Aborted");
+ }
+ Utils.delay(500L);
+ }
+ return mResultNetwork;
+ }
+
+ private Network networkMatch() {
+ if (mFlowType == FlowType.Provisioning) {
+ OSUInfo match = mOSUClient.getOSUInfo();
+ WifiConfiguration config = mPlatformAdapter.getActiveWifiConfig();
+ if (config != null && bssidMatch(match, mPlatformAdapter)
+ && Utils.decodeSsid(config.SSID).equals(match.getOsuSsid())) {
+ synchronized (mNetworkLock) {
+ mWifiNetworkId = config.networkId;
+ }
+ return mPlatformAdapter.getCurrentNetwork();
+ }
+ } else {
+ WifiConfiguration config = mPlatformAdapter.getActiveWifiConfig();
+ synchronized (mNetworkLock) {
+ mWifiNetworkId = config != null ? config.networkId : -1;
+ }
+ return mNetwork;
+ }
+ return null;
+ }
+
+ private void networkChange() {
+ WifiInfo connectionInfo = mPlatformAdapter.getConnectionInfo();
+ if (connectionInfo == null) {
+ return;
+ }
+ Network network = mPlatformAdapter.getCurrentNetwork();
+ Log.d(OSUManager.TAG, "New network " + network
+ + ", current OSU " + mOSUClient.getOSUInfo() +
+ ", addr " + Utils.toIpString(connectionInfo.getIpAddress()));
+
+ synchronized (mNetworkLock) {
+ if (mResultNetwork == null && network != null && connectionInfo.getIpAddress() != 0
+ && connectionInfo.getNetworkId() == mWifiNetworkId) {
+ mResultNetwork = network;
+ mNetworkLock.notifyAll();
+ }
+ }
+ }
+
+ public boolean createdNetwork() {
+ synchronized (mNetworkLock) {
+ return mNetworkCreated;
+ }
+ }
+
+ public FlowType getFlowType() {
+ return mFlowType;
+ }
+
+ public PlatformAdapter getPlatformAdapter() {
+ return mPlatformAdapter;
+ }
+
+ private void setLaunchTime() {
+ mLaunchTime = SystemClock.currentThreadTimeMillis();
+ }
+
+ public long getLaunchTime() {
+ return mLaunchTime;
+ }
+
+ private int getWifiNetworkId() {
+ synchronized (mNetworkLock) {
+ return mWifiNetworkId;
+ }
+ }
+
+ @Override
+ public void run() {
+ try {
+ Network network = connect();
+ Log.d(OSUManager.TAG, "OSU SSID Associated at " + network);
+
+ if (mFlowType == FlowType.Provisioning) {
+ mOSUClient.provision(mPlatformAdapter, network, mKeyManager);
+ } else {
+ mOSUClient.remediate(mPlatformAdapter, network,
+ mKeyManager, mHomeSP, mFlowType);
+ }
+ } catch (Throwable t) {
+ if (mAborted) {
+ Log.d(OSUManager.TAG, "OSU flow aborted: " + t, t);
+ } else {
+ Log.w(OSUManager.TAG, "OSU flow failed: " + t, t);
+ mPlatformAdapter.provisioningFailed(mSpName, t.getMessage());
+ }
+ } finally {
+ if (!mAborted) {
+ mOSUClient.close(false);
+ }
+ }
+ }
+
+ public void abort() {
+ synchronized (mNetworkLock) {
+ mAborted = true;
+ mNetworkLock.notifyAll();
+ }
+ // Sockets cannot be closed on the main thread...
+ // TODO: Might want to change this to a handler.
+ new Thread() {
+ @Override
+ public void run() {
+ try {
+ mOSUClient.close(true);
+ } catch (Throwable t) {
+ Log.d(OSUManager.TAG, "Exception aborting " + toString());
+ }
+ }
+ }.start();
+ }
+
+ @Override
+ public String toString() {
+ return mFlowType + " for " + mSpName;
+ }
+ }
+
+ private class FlowWorker extends Thread {
+ private final PlatformAdapter mPlatformAdapter;
+
+ private FlowWorker(PlatformAdapter platformAdapter) {
+ mPlatformAdapter = platformAdapter;
+ }
+
+ @Override
+ public void run() {
+ for (; ; ) {
+ synchronized (mQueue) {
+ if (mCurrent != null && mCurrent.createdNetwork()
+ && (mQueue.isEmpty() || mCurrent.deleteNetwork(mQueue.getLast()))) {
+ mPlatformAdapter.deleteNetwork(mCurrent.getWifiNetworkId());
+ }
+
+ mCurrent = null;
+ while (mQueue.isEmpty()) {
+ try {
+ mQueue.wait(WAIT_QUANTA);
+ } catch (InterruptedException ie) {
+ return;
+ }
+ if (mQueue.isEmpty()) {
+ // Bail out on time out
+ Log.d(OSUManager.TAG, "Flow worker terminating.");
+ mWorker = null;
+ mContext.stopService(new Intent(mContext, FlowService.class));
+ return;
+ }
+ }
+ mCurrent = mQueue.removeLast();
+ mCurrent.setLaunchTime();
+ }
+ Log.d(OSUManager.TAG, "Starting " + mCurrent);
+ mCurrent.run();
+ Log.d(OSUManager.TAG, "Exiting " + mCurrent);
+ }
+ }
+ }
+
+ /*
+ * Provisioning: Wait until there is an active WiFi info and the active WiFi config
+ * matches SSID and optionally BSSID.
+ * WNM Remediation: Wait until the active WiFi info matches BSSID.
+ * Timed remediation: The network is given (may be cellular).
+ */
+
+ public void appendFlow(OSUFlow flow) {
+ synchronized (mQueue) {
+ if (mCurrent != null &&
+ SystemClock.currentThreadTimeMillis()
+ - mCurrent.getLaunchTime() >= WAIT_TIMEOUT) {
+ Log.d(OSUManager.TAG, "Aborting stale OSU flow " + mCurrent);
+ mCurrent.abort();
+ mCurrent = null;
+ }
+
+ if (flow.getFlowType() == FlowType.Provisioning) {
+ // Kill any outstanding provisioning flows.
+ Iterator flows = mQueue.iterator();
+ while (flows.hasNext()) {
+ if (flows.next().getFlowType() == FlowType.Provisioning) {
+ flows.remove();
+ }
+ }
+
+ if (mCurrent != null
+ && mCurrent.getFlowType() == FlowType.Provisioning) {
+ Log.d(OSUManager.TAG, "Aborting current provisioning flow " + mCurrent);
+ mCurrent.abort();
+ mCurrent = null;
+ }
+
+ mQueue.addLast(flow);
+ } else {
+ mQueue.addFirst(flow);
+ }
+
+ if (mWorker == null) {
+ // TODO: Might want to change this to a handler.
+ mWorker = new FlowWorker(flow.getPlatformAdapter());
+ mWorker.start();
+ }
+
+ mQueue.notifyAll();
+ }
+ }
+
+ public void networkChange() {
+ OSUFlow pending;
+ synchronized (mQueue) {
+ pending = mCurrent;
+ }
+ Log.d(OSUManager.TAG, "Network change, current flow: " + pending);
+ if (pending != null) {
+ pending.networkChange();
+ }
+ }
+
+ private static boolean bssidMatch(OSUInfo osuInfo, PlatformAdapter platformAdapter) {
+ if (MATCH_BSSID) {
+ WifiInfo wifiInfo = platformAdapter.getConnectionInfo();
+ return wifiInfo != null && Utils.parseMac(wifiInfo.getBSSID()) == osuInfo.getOSUBssid();
+ } else {
+ return true;
+ }
+ }
+}
diff --git a/packages/Osu/src/com/android/hotspot2/osu/OSUManager.java b/packages/Osu/src/com/android/hotspot2/osu/OSUManager.java
index b8ca3fe270205..24cd10f1e583c 100644
--- a/packages/Osu/src/com/android/hotspot2/osu/OSUManager.java
+++ b/packages/Osu/src/com/android/hotspot2/osu/OSUManager.java
@@ -1,111 +1,55 @@
package com.android.hotspot2.osu;
+import android.content.ComponentName;
import android.content.Context;
-import android.net.Network;
+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.Constants;
import com.android.anqp.HSIconFileElement;
import com.android.anqp.OSUProvider;
import com.android.hotspot2.AppBridge;
-import com.android.hotspot2.OMADMAdapter;
import com.android.hotspot2.PasspointMatch;
import com.android.hotspot2.Utils;
-import com.android.hotspot2.WifiNetworkAdapter;
-import com.android.hotspot2.omadm.MOManager;
-import com.android.hotspot2.omadm.MOTree;
-import com.android.hotspot2.osu.commands.MOData;
-import com.android.hotspot2.osu.service.RedirectListener;
-import com.android.hotspot2.osu.service.SubscriptionTimer;
-import com.android.hotspot2.pps.HomeSP;
-import com.android.hotspot2.pps.UpdateInfo;
-
-import org.xml.sax.SAXException;
+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.io.FileInputStream;
-import java.io.FileOutputStream;
-import java.io.IOException;
import java.net.MalformedURLException;
-import java.net.URL;
-import java.security.GeneralSecurityException;
-import java.security.KeyStore;
-import java.security.KeyStoreException;
-import java.security.PrivateKey;
-import java.security.cert.Certificate;
-import java.security.cert.X509Certificate;
import java.util.ArrayList;
import java.util.Collection;
-import java.util.Enumeration;
import java.util.HashMap;
import java.util.HashSet;
-import java.util.Iterator;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
-import java.util.concurrent.LinkedBlockingQueue;
-import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
-import javax.net.ssl.KeyManager;
-
public class OSUManager {
public static final String TAG = "OSUMGR";
- public static final boolean R2_ENABLED = true;
public static final boolean R2_MOCK = true;
- private static final boolean MATCH_BSSID = false;
-
- private static final String KEYSTORE_FILE = "passpoint.ks";
-
- private static final String OSU_COUNT = "osu-count";
- private static final String SP_NAME = "sp-name";
- private static final String PROV_SUCCESS = "prov-success";
- private static final String DEAUTH = "deauth";
- private static final String DEAUTH_DELAY = "deauth-delay";
- private static final String DEAUTH_URL = "deauth-url";
- private static final String PROV_MESSAGE = "prov-message";
-
- private static final long REMEDIATION_TIMEOUT = 120000L;
- // How many scan result batches to hang on to
-
- public enum FlowType {Provisioning, Remediation, Policy}
-
- public static final String CERT_WFA_ALIAS = "wfa-root-";
- public static final String CERT_REM_ALIAS = "rem-";
- public static final String CERT_POLICY_ALIAS = "pol-";
- public static final String CERT_SHARED_ALIAS = "shr-";
- public static final String CERT_CLT_CERT_ALIAS = "clt-";
- public static final String CERT_CLT_KEY_ALIAS = "prv-";
- public static final String CERT_CLT_CA_ALIAS = "aaa-";
- private static final long THREAD_TIMEOUT = 10; // Seconds
+ private static final String REMEDIATION_FILE = "remediation.state";
// Preferred icon parameters
public static final Locale LOCALE = java.util.Locale.getDefault();
- private final Object mFlowLock = new Object();
- private final LinkedBlockingQueue mWorkQueue = new LinkedBlockingQueue<>();
- private FlowRunner mFlowThread;
-
- private final WifiNetworkAdapter mWifiNetworkAdapter;
-
private final AppBridge mAppBridge;
private final Context mContext;
private final IconCache mIconCache;
- private final SubscriptionTimer mSubscriptionTimer;
+ private final RemediationHandler mRemediationHandler;
private final Set mOSUSSIDs = new HashSet<>();
private final Map mOSUMap = new HashMap<>();
- private final File mKeyStoreFile;
- private final KeyStore mKeyStore;
- private volatile RedirectListener mRedirectListener;
private final AtomicInteger mOSUSequence = new AtomicInteger();
- private volatile Network mActiveNetwork;
- private volatile FlowWorker mRemediationFlow;
- private volatile OSUInfo mPendingOSU;
- private volatile Integer mOSUNwkID;
private final OSUCache mOSUCache;
@@ -113,315 +57,31 @@ public class OSUManager {
mContext = context;
mAppBridge = new AppBridge(context);
mIconCache = new IconCache(this);
- mWifiNetworkAdapter = new WifiNetworkAdapter(context, this);
- mSubscriptionTimer = new SubscriptionTimer(this, mWifiNetworkAdapter, context);
+ File appFolder = context.getFilesDir();
+ mRemediationHandler =
+ new RemediationHandler(context, new File(appFolder, REMEDIATION_FILE));
mOSUCache = new OSUCache();
- mKeyStoreFile = new File(context.getFilesDir(), KEYSTORE_FILE);
- Log.d(TAG, "KS file: " + mKeyStoreFile.getPath());
- KeyStore ks = null;
- try {
- //ks = loadKeyStore(KEYSTORE_FILE, readCertsFromDisk(WFA_CA_LOC));
- ks = loadKeyStore(mKeyStoreFile, OSUSocketFactory.buildCertSet());
- } catch (IOException e) {
- Log.e(TAG, "Failed to initialize Passpoint keystore, OSU disabled", e);
- }
- mKeyStore = ks;
}
- private static KeyStore loadKeyStore(File ksFile, Set diskCerts)
- throws IOException {
- try {
- KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType());
- if (ksFile.exists()) {
- try (FileInputStream in = new FileInputStream(ksFile)) {
- keyStore.load(in, null);
- }
-
- // Note: comparing two sets of certs does not work.
- boolean mismatch = false;
- int loadCount = 0;
- for (int n = 0; n < 1000; n++) {
- String alias = String.format("%s%d", CERT_WFA_ALIAS, n);
- Certificate cert = keyStore.getCertificate(alias);
- if (cert == null) {
- break;
- }
-
- loadCount++;
- boolean matched = false;
- Iterator iter = diskCerts.iterator();
- while (iter.hasNext()) {
- X509Certificate diskCert = iter.next();
- if (cert.equals(diskCert)) {
- iter.remove();
- matched = true;
- break;
- }
- }
- if (!matched) {
- mismatch = true;
- break;
- }
- }
- if (mismatch || !diskCerts.isEmpty()) {
- Log.d(TAG, "Re-seeding Passpoint key store with " +
- diskCerts.size() + " WFA certs");
- for (int n = 0; n < 1000; n++) {
- String alias = String.format("%s%d", CERT_WFA_ALIAS, n);
- Certificate cert = keyStore.getCertificate(alias);
- if (cert == null) {
- break;
- } else {
- keyStore.deleteEntry(alias);
- }
- }
- int index = 0;
- for (X509Certificate caCert : diskCerts) {
- keyStore.setCertificateEntry(
- String.format("%s%d", CERT_WFA_ALIAS, index), caCert);
- index++;
- }
-
- try (FileOutputStream out = new FileOutputStream(ksFile)) {
- keyStore.store(out, null);
- }
- } else {
- Log.d(TAG, "Loaded Passpoint key store with " + loadCount + " CA certs");
- Enumeration aliases = keyStore.aliases();
- while (aliases.hasMoreElements()) {
- Log.d("ZXC", "KS Alias '" + aliases.nextElement() + "'");
- }
- }
- } else {
- keyStore.load(null, null);
- int index = 0;
- for (X509Certificate caCert : diskCerts) {
- keyStore.setCertificateEntry(
- String.format("%s%d", CERT_WFA_ALIAS, index), caCert);
- index++;
- }
-
- try (FileOutputStream out = new FileOutputStream(ksFile)) {
- keyStore.store(out, null);
- }
- Log.d(TAG, "Initialized Passpoint key store with " +
- diskCerts.size() + " CA certs");
- }
- return keyStore;
- } catch (GeneralSecurityException gse) {
- throw new IOException(gse);
- }
+ public Context getContext() {
+ return mContext;
}
- public KeyStore getKeyStore() {
- return mKeyStore;
- }
-
- private static class FlowWorker implements Runnable {
- private final OSUClient mOSUClient;
- private final OSUManager mOSUManager;
- private final HomeSP mHomeSP;
- private final String mSpName;
- private final FlowType mFlowType;
- private final KeyManager mKeyManager;
- private final long mLaunchTime;
- private final Network mNetwork;
-
- private FlowWorker(Network network, OSUInfo osuInfo, OSUManager osuManager, KeyManager km)
- throws MalformedURLException {
- mNetwork = network;
- mOSUClient = new OSUClient(osuInfo, osuManager.getKeyStore());
- mOSUManager = osuManager;
- mHomeSP = null;
- mSpName = osuInfo.getName(LOCALE);
- mFlowType = FlowType.Provisioning;
- mKeyManager = km;
- mLaunchTime = System.currentTimeMillis();
- }
-
- private FlowWorker(Network network, String osuURL, OSUManager osuManager, KeyManager km,
- HomeSP homeSP, FlowType flowType) throws MalformedURLException {
- mNetwork = network;
- mOSUClient = new OSUClient(osuURL, osuManager.getKeyStore());
- mOSUManager = osuManager;
- mHomeSP = homeSP;
- mSpName = homeSP.getFriendlyName();
- mFlowType = flowType;
- mKeyManager = km;
- mLaunchTime = System.currentTimeMillis();
- }
-
- public long getLaunchTime() {
- return mLaunchTime;
- }
-
- private Network getNetwork() {
- return mNetwork;
- }
-
- @Override
- public void run() {
- Log.d(TAG, "OSU SSID Associated at " + mNetwork);
- try {
- if (mFlowType == FlowType.Provisioning) {
- mOSUClient.provision(mOSUManager, mNetwork, mKeyManager);
- } else {
- mOSUClient.remediate(mOSUManager, mNetwork, mKeyManager, mHomeSP, mFlowType);
- }
- } catch (Throwable t) {
- Log.w(TAG, "OSU flow failed: " + t, t);
- mOSUManager.provisioningFailed(mSpName, t.getMessage(), mHomeSP, mFlowType);
- }
- }
-
- @Override
- public String toString() {
- return mFlowType + " for " + mSpName;
- }
- }
-
- private static class FlowRunner extends Thread {
- private final LinkedBlockingQueue mWorkQueue;
- private final OSUManager mOSUManager;
-
- private FlowRunner(LinkedBlockingQueue workQueue, OSUManager osuManager) {
- mWorkQueue = workQueue;
- mOSUManager = osuManager;
- setDaemon(true);
- setName("OSU Client Thread");
- }
-
- @Override
- public void run() {
- for (;;) {
- FlowWorker flowWorker;
- try {
- flowWorker = mWorkQueue.poll(THREAD_TIMEOUT, TimeUnit.SECONDS);
- } catch (InterruptedException ie) {
- flowWorker = null;
- }
- if (flowWorker == null) {
- if (mOSUManager.serviceThreadExit()) {
- return;
- } else {
- continue;
- }
- }
- Log.d(TAG, "Starting " + flowWorker);
- flowWorker.run();
- }
- }
- }
-
- private void startOsuFlow(FlowWorker flowWorker) {
- synchronized (mFlowLock) {
- mWorkQueue.offer(flowWorker);
- if (mFlowThread == null) {
- mFlowThread = new FlowRunner(mWorkQueue, this);
- mFlowThread.start();
- }
- }
- }
-
- private boolean serviceThreadExit() {
- synchronized (mFlowLock) {
- if (mWorkQueue.isEmpty()) {
- mFlowThread = null;
- return true;
- } else {
- return false;
- }
- }
- }
-
- /*
- public void startOSU() {
- registerUserInputListener(new UserInputListener() {
- @Override
- public void requestUserInput(URL target, Network network, URL endRedirect) {
- Log.d(TAG, "Browser to " + target + ", land at " + endRedirect);
-
- final Intent intent = new Intent(
- ConnectivityManager.ACTION_CAPTIVE_PORTAL_SIGN_IN);
- intent.putExtra(ConnectivityManager.EXTRA_NETWORK, network);
- intent.putExtra(ConnectivityManager.EXTRA_CAPTIVE_PORTAL,
- new CaptivePortal(new ICaptivePortal.Stub() {
- @Override
- public void appResponse(int response) {
- }
- }));
- //intent.setData(Uri.parse(target.toString())); !!! Doesn't work!
- intent.putExtra(ConnectivityManager.EXTRA_CAPTIVE_PORTAL_URL, target.toString());
- intent.setFlags(
- Intent.FLAG_ACTIVITY_BROUGHT_TO_FRONT | Intent.FLAG_ACTIVITY_NEW_TASK);
- mContext.startActivityAsUser(intent, UserHandle.CURRENT);
- }
-
- @Override
- public String operationStatus(String spIdentity, OSUOperationStatus status,
- String message) {
- Log.d(TAG, "OSU OP Status: " + status + ", message " + message);
- Intent intent = new Intent(Intent.ACTION_OSU_NOTIFICATION);
- intent.putExtra(SP_NAME, spIdentity);
- intent.putExtra(PROV_SUCCESS, status == OSUOperationStatus.ProvisioningSuccess);
- if (message != null) {
- intent.putExtra(PROV_MESSAGE, message);
- }
- intent.setFlags(
- Intent.FLAG_ACTIVITY_BROUGHT_TO_FRONT | Intent.FLAG_ACTIVITY_NEW_TASK);
- mContext.startActivityAsUser(intent, UserHandle.CURRENT);
- return null;
- }
-
- @Override
- public void deAuthNotification(String spIdentity, boolean ess, int delay, URL url) {
- Log.i(TAG, "De-authentication imminent for " + (ess ? "ess" : "bss") +
- ", redirect to " + url);
- Intent intent = new Intent(Intent.ACTION_OSU_NOTIFICATION);
- intent.putExtra(SP_NAME, spIdentity);
- intent.putExtra(DEAUTH, ess);
- intent.putExtra(DEAUTH_DELAY, delay);
- intent.putExtra(DEAUTH_URL, url.toString());
- intent.setFlags(
- Intent.FLAG_ACTIVITY_BROUGHT_TO_FRONT | Intent.FLAG_ACTIVITY_NEW_TASK);
- mContext.startActivityAsUser(intent, UserHandle.CURRENT);
- }
- });
- addOSUListener(new OSUListener() {
- @Override
- public void osuNotification(int count) {
- Intent intent = new Intent(Intent.ACTION_OSU_NOTIFICATION);
- intent.putExtra(OSU_COUNT, count);
- intent.setFlags(
- Intent.FLAG_ACTIVITY_BROUGHT_TO_FRONT | Intent.FLAG_ACTIVITY_NEW_TASK);
- mContext.startActivityAsUser(intent, UserHandle.CURRENT);
- }
- });
- mWifiNetworkAdapter.initialize();
- mSubscriptionTimer.checkUpdates();
- }
- */
-
- public List getAvailableOSUs() {
+ public List getAvailableOSUs() {
synchronized (mOSUMap) {
- List completeOSUs = new ArrayList<>();
+ List completeOSUs = new ArrayList<>();
for (OSUInfo osuInfo : mOSUMap.values()) {
if (osuInfo.getIconStatus() == OSUInfo.IconStatus.Available) {
- completeOSUs.add(osuInfo);
+ completeOSUs.add(new OSUData(osuInfo));
}
}
return completeOSUs;
}
}
- public void recheckTimers() {
- mSubscriptionTimer.checkUpdates();
- }
-
public void setOSUSelection(int osuID) {
OSUInfo selection = null;
for (OSUInfo osuInfo : mOSUMap.values()) {
- Log.d("ZXZ", "In select: " + osuInfo + ", id " + osuInfo.getOsuID());
if (osuInfo.getOsuID() == osuID &&
osuInfo.getIconStatus() == OSUInfo.IconStatus.Available) {
selection = osuInfo;
@@ -429,137 +89,84 @@ public class OSUManager {
}
}
- Log.d(TAG, "Selected OSU ID " + osuID + ", matches " + selection);
+ Log.d(TAG, "Selected OSU ID " + osuID + ": " + selection);
if (selection == null) {
- mPendingOSU = null;
return;
}
- mPendingOSU = selection;
- WifiConfiguration config = mWifiNetworkAdapter.getActiveWifiConfig();
+ final OSUInfo osu = selection;
- if (config != null &&
- bssidMatch(selection) &&
- Utils.unquote(config.SSID).equals(selection.getOsuSsid())) {
-
- try {
- // Go straight to provisioning if the network is already selected.
- // Also note that mOSUNwkID is left unset to leave the network around after
- // flow completion since it was not added by the OSU flow.
- initiateProvisioning(mPendingOSU, mWifiNetworkAdapter.getCurrentNetwork());
- } catch (IOException ioe) {
- notifyUser(OSUOperationStatus.ProvisioningFailure, ioe.getMessage(),
- mPendingOSU.getName(LOCALE));
- } finally {
- mPendingOSU = null;
+ 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);
+ }
}
- } else {
- try {
- mOSUNwkID = mWifiNetworkAdapter.connect(selection, mPendingOSU.getName(LOCALE));
- } catch (IOException ioe) {
- notifyUser(OSUOperationStatus.ProvisioningFailure, ioe.getMessage(),
- selection.getName(LOCALE));
+
+ @Override
+ public void onServiceDisconnected(ComponentName name) {
+ Log.d(OSUManager.TAG, "Service disconnect: " + name);
}
- }
+ }, Context.BIND_AUTO_CREATE);
}
- public void networkDeleted(WifiConfiguration configuration) {
- Log.d("ZXZ", "Network deleted: " + configuration.FQDN);
- HomeSP homeSP = mWifiNetworkAdapter.getHomeSP(configuration);
- if (homeSP != null) {
- spDeleted(homeSP.getFQDN());
+ 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 networkChanged(WifiConfiguration configuration) {
- mWifiNetworkAdapter.networkConfigChange(configuration);
+ public void networkConnectChange(WifiInfo newNetwork) {
+ mRemediationHandler.newConnection(newNetwork);
}
- public void networkConnectEvent(WifiInfo wifiInfo) {
- if (wifiInfo != null) {
- setActiveNetwork(mWifiNetworkAdapter.getActiveWifiConfig(),
- mWifiNetworkAdapter.getCurrentNetwork());
- }
+ public void networkConfigChanged() {
+ mRemediationHandler.networkConfigChange();
}
public void wifiStateChange(boolean on) {
- if (!on) {
- int current = mOSUMap.size();
- mOSUMap.clear();
- mOSUCache.clearAll();
- mIconCache.tick(true);
- if (current > 0) {
- notifyOSUCount();
- }
- }
- }
-
- private boolean bssidMatch(OSUInfo osuInfo) {
- if (MATCH_BSSID) {
- WifiInfo wifiInfo = mWifiNetworkAdapter.getConnectionInfo();
- return wifiInfo != null && Utils.parseMac(wifiInfo.getBSSID()) == osuInfo.getBSSID();
- } else {
- return true;
- }
- }
-
- public void setActiveNetwork(WifiConfiguration wifiConfiguration, Network network) {
- Log.d(TAG, "Network change: " + network + ", cfg " +
- (wifiConfiguration != null ? wifiConfiguration.SSID : "-") + ", osu " + mPendingOSU);
- mActiveNetwork = network;
- if (mPendingOSU != null &&
- wifiConfiguration != null &&
- network != null &&
- bssidMatch(mPendingOSU) &&
- Utils.unquote(wifiConfiguration.SSID).equals(mPendingOSU.getOsuSsid())) {
-
- try {
- Log.d(TAG, "New network " + network + ", current OSU " + mPendingOSU);
- initiateProvisioning(mPendingOSU, network);
- } catch (IOException ioe) {
- notifyUser(OSUOperationStatus.ProvisioningFailure, ioe.getMessage(),
- mPendingOSU.getName(LOCALE));
- } finally {
- mPendingOSU = null;
- }
+ if (on) {
+ return;
}
- if (mRemediationFlow != null && network != null &&
- mRemediationFlow.getNetwork().netId == network.netId) {
- startOsuFlow(mRemediationFlow);
- mRemediationFlow = null;
+ // 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();
}
}
-
- /**
- * Called when an OSU has been selected and the associated network is fully connected.
- *
- * @param osuInfo The selected OSUInfo or null if the current OSU flow is cancelled externally,
- * e.g. WiFi is turned off or the OSU network is otherwise detected as
- * unreachable.
- * @param network The currently associated network (for the OSU SSID).
- * @throws IOException
- * @throws GeneralSecurityException
- */
- private void initiateProvisioning(OSUInfo osuInfo, Network network)
- throws IOException {
- startOsuFlow(new FlowWorker(network, osuInfo, this, getKeyManager(null, mKeyStore)));
- }
-
- /**
- * @param homeSP The Home SP associated with the keying material in question. Passing
- * null returns a "system wide" KeyManager to support pre-provisioned certs based
- * on names retrieved from the ClientCertInfo request.
- * @return A key manager suitable for the given configuration (or pre-provisioned keys).
- */
- private static KeyManager getKeyManager(HomeSP homeSP, KeyStore keyStore)
- throws IOException {
- return homeSP != null ? new ClientKeyManager(homeSP, keyStore) :
- new WiFiKeyManager(keyStore);
- }
-
public boolean isOSU(String ssid) {
synchronized (mOSUMap) {
return mOSUSSIDs.contains(ssid);
@@ -581,17 +188,17 @@ public class OSUManager {
long bssid = Utils.parseMac(entry.getValue().BSSID);
if (existing == null) {
- osus.put(entry.getKey(), new OSUInfo(entry.getValue(), entry.getKey().getSSID(),
- entry.getKey(), mOSUSequence.getAndIncrement()));
+ 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().getSSID(),
- entry.getKey(), existing.getOsuID());
+ 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().getSSID(),
+ osus.put(entry.getKey(), new OSUInfo(entry.getValue(),
entry.getKey(), mOSUSequence.getAndIncrement()));
}
} else {
@@ -622,7 +229,8 @@ public class OSUManager {
}
public void doIconQuery(long bssid, String fileName) {
- mWifiNetworkAdapter.doIconQuery(bssid, fileName);
+ WifiManager wifiManager = (WifiManager) mContext.getSystemService(Context.WIFI_SERVICE);
+ wifiManager.queryPasspointIcon(bssid, fileName);
}
private void notifyOSUCount() {
@@ -633,292 +241,24 @@ public class OSUManager {
}
}
Log.d(TAG, "Latest OSU info: " + count + " with icons, map " + mOSUMap);
- mAppBridge.showOsuCount(count, getAvailableOSUs());
+ mAppBridge.showOsuCount(count);
}
- public void deauth(long bssid, boolean ess, int delay, String url) throws MalformedURLException {
+ 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));
- mWifiNetworkAdapter.setHoldoffTime(delay * Constants.MILLIS_IN_A_SEC, ess);
- HomeSP homeSP = mWifiNetworkAdapter.getCurrentSP();
- String spName = homeSP != null ? homeSP.getFriendlyName() : "unknown";
+ 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);
}
- // !!! Consistently check passpoint match.
- public void wnmRemediate(long bssid, String url, PasspointMatch match)
- throws IOException, SAXException {
- HomeSP homeSP = mWifiNetworkAdapter.getCurrentSP();
- if (homeSP == null) {
- throw new IOException("Remediation request on unidentified Passpoint network ");
- }
- Network network = mWifiNetworkAdapter.getCurrentNetwork();
- if (network == null) {
- throw new IOException("Failed to determine current network");
- }
- WifiInfo wifiInfo = mWifiNetworkAdapter.getConnectionInfo();
- if (wifiInfo == null || Utils.parseMac(wifiInfo.getBSSID()) != bssid) {
- throw new IOException("Mismatching BSSID");
- }
- Log.d(TAG, "WNM Remediation on " + network.netId + " FQDN " + homeSP.getFQDN());
-
- FlowWorker flowWorker = new FlowWorker(network, url, this,
- getKeyManager(homeSP, mKeyStore), homeSP, FlowType.Remediation);
-
- if (mActiveNetwork != null && wifiInfo.getNetworkId() == mActiveNetwork.netId) {
- startOsuFlow(flowWorker);
- } else {
- mRemediationFlow = flowWorker;
- }
+ public void wnmRemediate(final long bssid, final String url, PasspointMatch match) {
+ mRemediationHandler.wnmReceived(bssid, url);
}
- public void remediate(HomeSP homeSP, boolean policy) throws IOException, SAXException {
- UpdateInfo updateInfo;
- if (policy) {
- if (homeSP.getPolicy() == null) {
- throw new IOException("No policy object");
- }
- updateInfo = homeSP.getPolicy().getPolicyUpdate();
- } else {
- updateInfo = homeSP.getSubscriptionUpdate();
- }
- switch (updateInfo.getUpdateRestriction()) {
- case HomeSP: {
- Network network = mWifiNetworkAdapter.getCurrentNetwork();
- if (network == null) {
- throw new IOException("Failed to determine current network");
- }
-
- HomeSP activeSP = mWifiNetworkAdapter.getCurrentSP();
-
- if (activeSP == null || !activeSP.getFQDN().equals(homeSP.getFQDN())) {
- throw new IOException("Remediation restricted to HomeSP");
- }
- doRemediate(updateInfo.getURI(), network, homeSP, policy);
- break;
- }
- case RoamingPartner: {
- Network network = mWifiNetworkAdapter.getCurrentNetwork();
- if (network == null) {
- throw new IOException("Failed to determine current network");
- }
-
- WifiInfo wifiInfo = mWifiNetworkAdapter.getConnectionInfo();
- if (wifiInfo == null) {
- throw new IOException("Unable to determine WiFi info");
- }
-
- PasspointMatch match = mWifiNetworkAdapter.
- matchProviderWithCurrentNetwork(homeSP.getFQDN());
-
- if (match == PasspointMatch.HomeProvider ||
- match == PasspointMatch.RoamingProvider) {
- doRemediate(updateInfo.getURI(), network, homeSP, policy);
- } else {
- throw new IOException("No roaming network match: " + match);
- }
- break;
- }
- case Unrestricted: {
- Network network = mWifiNetworkAdapter.getCurrentNetwork();
- doRemediate(updateInfo.getURI(), network, homeSP, policy);
- break;
- }
- }
- }
-
- private void doRemediate(String url, Network network, HomeSP homeSP, boolean policy)
- throws IOException {
-
- startOsuFlow(new FlowWorker(network, url, this,
- getKeyManager(homeSP, mKeyStore),
- homeSP, policy ? FlowType.Policy : FlowType.Remediation));
- }
-
- public MOTree getMOTree(HomeSP homeSP) throws IOException {
- return mWifiNetworkAdapter.getMOTree(homeSP);
- }
-
- protected URL prepareUserInput(String spName) throws IOException {
- mRedirectListener = new RedirectListener(this, spName);
- return mRedirectListener.getURL();
- }
-
- protected boolean startUserInput(URL target, Network network) throws IOException {
- mRedirectListener.startService();
- mWifiNetworkAdapter.launchBrowser(target, network, mRedirectListener.getURL());
- return mRedirectListener.waitForUser();
- }
-
- public String notifyUser(OSUOperationStatus status, String message, String spName) {
- if (status == OSUOperationStatus.UserInputComplete) {
- return null;
- }
- if (mOSUNwkID != null) {
- // Delete the OSU network if it was added by the OSU flow
- mWifiNetworkAdapter.deleteNetwork(mOSUNwkID);
- mOSUNwkID = null;
- }
- mAppBridge.showStatus(status, spName, message, null);
- return null;
- }
-
- public void provisioningFailed(String spName, String message,
- HomeSP homeSP, FlowType flowType) {
- if (mRedirectListener != null) {
- mRedirectListener.abort();
- mRedirectListener = null;
- }
- notifyUser(OSUOperationStatus.ProvisioningFailure, message, spName);
- }
-
- public void provisioningComplete(OSUInfo osuInfo,
- MOData moData, Map> certs,
- PrivateKey privateKey, Network osuNetwork) {
- try {
- String xml = moData.getMOTree().toXml();
- HomeSP homeSP = MOManager.buildSP(xml);
-
- Integer spNwk = mWifiNetworkAdapter.addNetwork(homeSP, certs, privateKey, osuNetwork);
- if (spNwk == null) {
- notifyUser(OSUOperationStatus.ProvisioningFailure,
- "Failed to save network configuration", osuInfo.getName(LOCALE));
- } else {
- if (mWifiNetworkAdapter.addSP(xml) < 0) {
- mWifiNetworkAdapter.deleteNetwork(spNwk);
- Log.e(TAG, "Failed to provision: " + homeSP.getFQDN());
- notifyUser(OSUOperationStatus.ProvisioningFailure, "Failed to add MO",
- osuInfo.getName(LOCALE));
- return;
- }
- Set rootCerts = OSUSocketFactory.getRootCerts(mKeyStore);
- X509Certificate remCert = getCert(certs, OSUCertType.Remediation);
- X509Certificate polCert = getCert(certs, OSUCertType.Policy);
- int newCerts = 0;
- if (privateKey != null) {
- X509Certificate cltCert = getCert(certs, OSUCertType.Client);
- mKeyStore.setKeyEntry(CERT_CLT_KEY_ALIAS + homeSP.getFQDN(),
- privateKey, null, new X509Certificate[]{cltCert});
- mKeyStore.setCertificateEntry(CERT_CLT_CERT_ALIAS + homeSP.getFQDN(), cltCert);
- newCerts++;
- }
- boolean usingShared = false;
- if (remCert != null) {
- if (!rootCerts.contains(remCert)) {
- if (remCert.equals(polCert)) {
- mKeyStore.setCertificateEntry(CERT_SHARED_ALIAS + homeSP.getFQDN(),
- remCert);
- usingShared = true;
- newCerts++;
- } else {
- mKeyStore.setCertificateEntry(CERT_REM_ALIAS + homeSP.getFQDN(),
- remCert);
- newCerts++;
- }
- }
- }
- if (!usingShared && polCert != null) {
- if (!rootCerts.contains(polCert)) {
- mKeyStore.setCertificateEntry(CERT_POLICY_ALIAS + homeSP.getFQDN(),
- remCert);
- newCerts++;
- }
- }
-
- Log.d("ZXZ", "Got " + newCerts + " new certs.");
- if (newCerts > 0) {
- try (FileOutputStream out = new FileOutputStream(mKeyStoreFile)) {
- mKeyStore.store(out, null);
- }
- }
- notifyUser(OSUOperationStatus.ProvisioningSuccess, null, osuInfo.getName(LOCALE));
- Log.d(TAG, "Provisioning complete.");
- }
- } catch (IOException | GeneralSecurityException | SAXException e) {
- Log.e(TAG, "Failed to provision: " + e, e);
- notifyUser(OSUOperationStatus.ProvisioningFailure, e.toString(),
- osuInfo.getName(LOCALE));
- }
- }
-
- private static X509Certificate getCert(Map> certMap,
- OSUCertType certType) {
- List certs = certMap.get(certType);
- if (certs == null || certs.isEmpty()) {
- return null;
- }
- return certs.iterator().next();
- }
-
- public void spDeleted(String fqdn) {
- int count = deleteCerts(mKeyStore, fqdn,
- CERT_REM_ALIAS, CERT_POLICY_ALIAS, CERT_SHARED_ALIAS, CERT_CLT_CERT_ALIAS);
-
- Log.d(TAG, "Passpoint network deleted, removing " + count + " key store entries");
-
- try {
- if (mKeyStore.getKey(CERT_CLT_KEY_ALIAS + fqdn, null) != null) {
- mKeyStore.deleteEntry(CERT_CLT_KEY_ALIAS + fqdn);
- }
- } catch (GeneralSecurityException e) {
- /**/
- }
-
- if (count > 0) {
- try (FileOutputStream out = new FileOutputStream(mKeyStoreFile)) {
- mKeyStore.store(out, null);
- } catch (IOException | GeneralSecurityException e) {
- Log.w(TAG, "Failed to remove certs from key store: " + e);
- }
- }
- }
-
- private static int deleteCerts(KeyStore keyStore, String fqdn, String... prefixes) {
- int count = 0;
- for (String prefix : prefixes) {
- try {
- String alias = prefix + fqdn;
- Certificate cert = keyStore.getCertificate(alias);
- if (cert != null) {
- keyStore.deleteEntry(alias);
- count++;
- }
- } catch (KeyStoreException kse) {
- /**/
- }
- }
- return count;
- }
-
- public void remediationComplete(HomeSP homeSP, Collection mods,
- Map> certs,
- PrivateKey privateKey)
- throws IOException, GeneralSecurityException {
-
- HomeSP altSP = null;
- if (mWifiNetworkAdapter.modifySP(homeSP, mods) > 0) {
- altSP = MOManager.modifySP(homeSP, mWifiNetworkAdapter.getMOTree(homeSP), mods);
- }
-
- X509Certificate caCert = null;
- List clientCerts = null;
- if (certs != null) {
- List certList = certs.get(OSUCertType.AAA);
- caCert = certList != null && !certList.isEmpty() ? certList.iterator().next() : null;
- clientCerts = certs.get(OSUCertType.Client);
- }
- if (altSP != null || certs != null) {
- if (altSP == null) {
- altSP = homeSP;
- }
- mWifiNetworkAdapter.updateNetwork(altSP, caCert, clientCerts, privateKey);
- }
- notifyUser(OSUOperationStatus.ProvisioningSuccess, null, homeSP.getFriendlyName());
- }
-
- protected OMADMAdapter getOMADMAdapter() {
- return OMADMAdapter.getInstance(mContext);
+ public void remediationDone(String fqdn, boolean policy) {
+ mRemediationHandler.remediationDone(fqdn, policy);
}
}
diff --git a/packages/Osu/src/com/android/hotspot2/osu/OSUSocketFactory.java b/packages/Osu/src/com/android/hotspot2/osu/OSUSocketFactory.java
index e8335f77e0117..1f5547bf1f9af 100644
--- a/packages/Osu/src/com/android/hotspot2/osu/OSUSocketFactory.java
+++ b/packages/Osu/src/com/android/hotspot2/osu/OSUSocketFactory.java
@@ -5,6 +5,7 @@ import android.util.Base64;
import android.util.Log;
import com.android.hotspot2.Utils;
+import com.android.hotspot2.flow.PlatformAdapter;
import com.android.hotspot2.pps.HomeSP;
import java.io.ByteArrayInputStream;
@@ -76,7 +77,7 @@ public class OSUSocketFactory {
}
public static OSUSocketFactory getSocketFactory(KeyStore ks, HomeSP homeSP,
- OSUManager.FlowType flowType,
+ OSUFlowManager.FlowType flowType,
Network network, URL url, KeyManager km,
boolean enforceSecurity)
throws GeneralSecurityException, IOException {
@@ -87,7 +88,7 @@ public class OSUSocketFactory {
return new OSUSocketFactory(ks, homeSP, flowType, network, url, km);
}
- private OSUSocketFactory(KeyStore ks, HomeSP homeSP, OSUManager.FlowType flowType,
+ private OSUSocketFactory(KeyStore ks, HomeSP homeSP, OSUFlowManager.FlowType flowType,
Network network,
URL url, KeyManager km) throws GeneralSecurityException, IOException {
mNetwork = network;
@@ -217,10 +218,10 @@ public class OSUSocketFactory {
private static class WFATrustManager implements X509TrustManager {
private final KeyStore mKeyStore;
private final HomeSP mHomeSP;
- private final OSUManager.FlowType mFlowType;
+ private final OSUFlowManager.FlowType mFlowType;
private X509Certificate[] mTrustChain;
- private WFATrustManager(KeyStore ks, HomeSP homeSP, OSUManager.FlowType flowType)
+ private WFATrustManager(KeyStore ks, HomeSP homeSP, OSUFlowManager.FlowType flowType)
throws CertificateException {
mKeyStore = ks;
mHomeSP = homeSP;
@@ -250,12 +251,13 @@ public class OSUSocketFactory {
trustAnchors.add(new TrustAnchor(cert, null));
}
} else {
- String prefix = mFlowType == OSUManager.FlowType.Remediation ?
- OSUManager.CERT_REM_ALIAS : OSUManager.CERT_POLICY_ALIAS;
+ String prefix = mFlowType == OSUFlowManager.FlowType.Remediation ?
+ PlatformAdapter.CERT_REM_ALIAS : PlatformAdapter.CERT_POLICY_ALIAS;
X509Certificate cert = getCert(mKeyStore, prefix + mHomeSP.getFQDN());
if (cert == null) {
- cert = getCert(mKeyStore, OSUManager.CERT_SHARED_ALIAS + mHomeSP.getFQDN());
+ cert = getCert(mKeyStore,
+ PlatformAdapter.CERT_SHARED_ALIAS + mHomeSP.getFQDN());
}
if (cert == null) {
for (X509Certificate root : getRootCerts(mKeyStore)) {
@@ -300,7 +302,7 @@ public class OSUSocketFactory {
int index = 0;
for (int n = 0; n < 1000; n++) {
Certificate cert = keyStore.getCertificate(
- String.format("%s%d", OSUManager.CERT_WFA_ALIAS, index));
+ String.format("%s%d", PlatformAdapter.CERT_WFA_ALIAS, index));
if (cert == null) {
break;
} else if (cert instanceof X509Certificate) {
diff --git a/packages/Osu/src/com/android/hotspot2/osu/OSUWebView.java b/packages/Osu/src/com/android/hotspot2/osu/OSUWebView.java
new file mode 100644
index 0000000000000..afbd0d151b3d4
--- /dev/null
+++ b/packages/Osu/src/com/android/hotspot2/osu/OSUWebView.java
@@ -0,0 +1,91 @@
+package com.android.hotspot2.osu;
+
+import android.annotation.Nullable;
+import android.app.Activity;
+import android.graphics.Bitmap;
+import android.net.ConnectivityManager;
+import android.net.Network;
+import android.net.http.SslError;
+import android.os.Bundle;
+import android.util.Log;
+import android.util.TypedValue;
+import android.webkit.SslErrorHandler;
+import android.webkit.WebSettings;
+import android.webkit.WebView;
+import android.webkit.WebViewClient;
+
+import com.android.hotspot2.R;
+
+public class OSUWebView extends Activity {
+ public static final String OSU_URL = "com.android.hotspot2.osu.URL";
+ public static final String OSU_NETWORK = "com.android.hotspot2.osu.NETWORK";
+
+ private String mUrl;
+
+ @Override
+ protected void onCreate(@Nullable Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ Log.d(OSUManager.TAG, "Opening OSU Web View");
+
+ ConnectivityManager connectivityManager = ConnectivityManager.from(this);
+
+ mUrl = getIntent().getStringExtra(OSU_URL);
+ Network network = getIntent().getParcelableExtra(OSU_NETWORK);
+ connectivityManager.bindProcessToNetwork(network);
+
+ getActionBar().setDisplayShowHomeEnabled(false);
+ setContentView(R.layout.osu_web_view);
+ getActionBar().setDisplayShowHomeEnabled(false);
+
+ final WebView myWebView = (WebView) findViewById(R.id.webview);
+ myWebView.clearCache(true);
+ WebSettings webSettings = myWebView.getSettings();
+ webSettings.setJavaScriptEnabled(true);
+ MyWebViewClient mWebViewClient = new MyWebViewClient();
+ myWebView.setWebViewClient(mWebViewClient);
+ Log.d(OSUManager.TAG, "OSU Web View to " + mUrl);
+ myWebView.loadUrl(mUrl);
+ Log.d(OSUManager.TAG, "OSU Web View loading");
+ //myWebView.setWebChromeClient(new MyWebChromeClient());
+ // Start initial page load so WebView finishes loading proxy settings.
+ // Actual load of mUrl is initiated by MyWebViewClient.
+ //myWebView.loadData("", "text/html", null);
+ }
+
+ private class MyWebViewClient extends WebViewClient {
+ private static final String INTERNAL_ASSETS = "file:///android_asset/";
+ // How many Android device-independent-pixels per scaled-pixel
+ // dp/sp = (px/sp) / (px/dp) = (1/sp) / (1/dp)
+ private final float mDpPerSp = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, 1,
+ getResources().getDisplayMetrics()) /
+ TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 1,
+ getResources().getDisplayMetrics());
+ private int mPagesLoaded;
+
+ // If we haven't finished cleaning up the history, don't allow going back.
+ public boolean allowBack() {
+ return mPagesLoaded > 1;
+ }
+
+ // Convert Android device-independent-pixels (dp) to HTML size.
+ private String dp(int dp) {
+ // HTML px's are scaled just like dp's, so just add "px" suffix.
+ return Integer.toString(dp) + "px";
+ }
+
+ // Convert Android scaled-pixels (sp) to HTML size.
+ private String sp(int sp) {
+ // Convert sp to dp's.
+ float dp = sp * mDpPerSp;
+ // Apply a scale factor to make things look right.
+ dp *= 1.3;
+ // Convert dp's to HTML size.
+ return dp((int)dp);
+ }
+
+ @Override
+ public void onReceivedSslError(WebView view, SslErrorHandler handler, SslError error) {
+ Log.d(OSUManager.TAG, "TLS error in Web View: " + error);
+ }
+ }
+}
diff --git a/packages/Osu/src/com/android/hotspot2/osu/SPVerifier.java b/packages/Osu/src/com/android/hotspot2/osu/SPVerifier.java
index c5290534fafbd..f193b6a4a7cdc 100644
--- a/packages/Osu/src/com/android/hotspot2/osu/SPVerifier.java
+++ b/packages/Osu/src/com/android/hotspot2/osu/SPVerifier.java
@@ -15,6 +15,7 @@ import com.android.hotspot2.asn1.Asn1Octets;
import com.android.hotspot2.asn1.Asn1Oid;
import com.android.hotspot2.asn1.Asn1String;
import com.android.hotspot2.asn1.OidMappings;
+import com.android.hotspot2.flow.OSUInfo;
import java.io.IOException;
import java.nio.ByteBuffer;
diff --git a/packages/Osu/src/com/android/hotspot2/osu/service/RedirectListener.java b/packages/Osu/src/com/android/hotspot2/osu/service/RedirectListener.java
index ce26afdd7c509..105a96dddda31 100644
--- a/packages/Osu/src/com/android/hotspot2/osu/service/RedirectListener.java
+++ b/packages/Osu/src/com/android/hotspot2/osu/service/RedirectListener.java
@@ -2,7 +2,7 @@ package com.android.hotspot2.osu.service;
import android.util.Log;
-import com.android.hotspot2.osu.OSUManager;
+import com.android.hotspot2.flow.PlatformAdapter;
import com.android.hotspot2.osu.OSUOperationStatus;
import java.io.BufferedReader;
@@ -36,7 +36,7 @@ public class RedirectListener extends Thread {
"