summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--service/Android.mk3
-rw-r--r--service/java/com/android/server/wifi/hotspot2/OsuNetworkConnection.java8
-rw-r--r--service/java/com/android/server/wifi/hotspot2/OsuServerConnection.java80
-rw-r--r--service/java/com/android/server/wifi/hotspot2/PasspointObjectFactory.java2
-rw-r--r--service/java/com/android/server/wifi/hotspot2/PasspointProvisioner.java255
-rw-r--r--service/java/com/android/server/wifi/hotspot2/soap/RedirectListener.java143
-rw-r--r--tests/wifitests/src/com/android/server/wifi/hotspot2/OsuServerConnectionTest.java32
-rw-r--r--tests/wifitests/src/com/android/server/wifi/hotspot2/PasspointProvisionerTest.java132
-rw-r--r--tests/wifitests/src/com/android/server/wifi/hotspot2/soap/PostDevDataMessageTest.java1
-rw-r--r--tests/wifitests/src/com/android/server/wifi/hotspot2/soap/PostDevDataResponseTest.java3
-rw-r--r--tests/wifitests/src/com/android/server/wifi/hotspot2/soap/RedirectListenerTest.java172
-rw-r--r--tests/wifitests/src/com/android/server/wifi/hotspot2/soap/SoapParserTest.java3
-rw-r--r--tests/wifitests/src/com/android/server/wifi/hotspot2/soap/SppResponseMessageTest.java3
13 files changed, 735 insertions, 102 deletions
diff --git a/service/Android.mk b/service/Android.mk
index 21d776855..cf5cd8d1d 100644
--- a/service/Android.mk
+++ b/service/Android.mk
@@ -70,7 +70,8 @@ LOCAL_STATIC_JAVA_LIBRARIES := \
android.hardware.wifi.hostapd-V1.0-java \
android.hardware.wifi.supplicant-V1.0-java \
android.hardware.wifi.supplicant-V1.1-java \
- ksoap2
+ ksoap2 \
+ libnanohttpd
LOCAL_REQUIRED_MODULES := \
services \
diff --git a/service/java/com/android/server/wifi/hotspot2/OsuNetworkConnection.java b/service/java/com/android/server/wifi/hotspot2/OsuNetworkConnection.java
index ecfcc5a51..73dcca505 100644
--- a/service/java/com/android/server/wifi/hotspot2/OsuNetworkConnection.java
+++ b/service/java/com/android/server/wifi/hotspot2/OsuNetworkConnection.java
@@ -69,7 +69,7 @@ public class OsuNetworkConnection {
void onDisconnected();
/**
- * Invoked when a timer tracking connection request is not reset by successfull connection.
+ * Invoked when a timer tracking connection request is not reset by successful connection.
*/
void onTimeOut();
@@ -84,10 +84,6 @@ public class OsuNetworkConnection {
void onWifiDisabled();
}
- /**
- * Create an instance of {@link NetworkConnection} for the specified Wi-Fi network.
- * @param context The application context
- */
public OsuNetworkConnection(Context context) {
mContext = context;
}
@@ -146,6 +142,7 @@ public class OsuNetworkConnection {
/**
* Register for network and Wifi state events
+ *
* @param callbacks The callbacks to be invoked on network change events
*/
public void setEventCallback(Callbacks callbacks) {
@@ -206,6 +203,7 @@ public class OsuNetworkConnection {
/**
* Method to update logging level in this class
+ *
* @param verbose more than 0 enables verbose logging
*/
public void enableVerboseLogging(int verbose) {
diff --git a/service/java/com/android/server/wifi/hotspot2/OsuServerConnection.java b/service/java/com/android/server/wifi/hotspot2/OsuServerConnection.java
index ca05cd017..fd993e599 100644
--- a/service/java/com/android/server/wifi/hotspot2/OsuServerConnection.java
+++ b/service/java/com/android/server/wifi/hotspot2/OsuServerConnection.java
@@ -18,10 +18,14 @@ package com.android.server.wifi.hotspot2;
import android.annotation.NonNull;
import android.net.Network;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.os.Looper;
import android.text.TextUtils;
import android.util.Log;
import android.util.Pair;
+import com.android.internal.annotations.VisibleForTesting;
import com.android.org.conscrypt.TrustManagerImpl;
import com.android.server.wifi.hotspot2.soap.HttpsServiceConnection;
import com.android.server.wifi.hotspot2.soap.HttpsTransport;
@@ -65,12 +69,21 @@ public class OsuServerConnection {
private HttpsTransport mHttpsTransport;
private HttpsServiceConnection mServiceConnection = null;
private HttpsURLConnection mUrlConnection = null;
+ private HandlerThread mOsuServerHandlerThread;
+ private Handler mHandler;
private PasspointProvisioner.OsuServerCallbacks mOsuServerCallbacks;
private boolean mSetupComplete = false;
private boolean mVerboseLoggingEnabled = false;
+ private Looper mLooper;
+
+ @VisibleForTesting
+ /* package */ OsuServerConnection(Looper looper) {
+ mLooper = looper;
+ }
/**
* Sets up callback for event
+ *
* @param callbacks OsuServerCallbacks to be invoked for server related events
*/
public void setEventCallback(PasspointProvisioner.OsuServerCallbacks callbacks) {
@@ -79,6 +92,7 @@ public class OsuServerConnection {
/**
* Initialize socket factory for server connection using HTTPS
+ *
* @param tlsContext SSLContext that will be used for HTTPS connection
* @param trustManagerImpl TrustManagerImpl delegate to validate certs
*/
@@ -96,10 +110,19 @@ public class OsuServerConnection {
return;
}
mSetupComplete = true;
+
+ // If mLooper is already set by unit test, don't overwrite it.
+ if (mLooper == null) {
+ mOsuServerHandlerThread = new HandlerThread("OsuServerHandler");
+ mOsuServerHandlerThread.start();
+ mLooper = mOsuServerHandlerThread.getLooper();
+ }
+ mHandler = new Handler(mLooper);
}
/**
* Provides the capability to run OSU server validation
+ *
* @return boolean true if capability available
*/
public boolean canValidateServer() {
@@ -108,6 +131,7 @@ public class OsuServerConnection {
/**
* Enables verbose logging
+ *
* @param verbose a value greater than zero enables verbose logging
*/
public void enableVerboseLogging(int verbose) {
@@ -116,11 +140,12 @@ public class OsuServerConnection {
/**
* Connect to the OSU server
+ *
* @param url Osu Server's URL
* @param network current network connection
* @return boolean value, true if connection was successful
*
- * Relies on the caller to ensure that the capability to validate the OSU
+ * Note: Relies on the caller to ensure that the capability to validate the OSU
* Server is available.
*/
public boolean connect(URL url, Network network) {
@@ -180,24 +205,30 @@ public class OsuServerConnection {
* The helper method to exchange a SOAP message.
*
* @param soapEnvelope the soap message to be sent.
- * @return {@link SppResponseMessage} parsed, {@code null} in any failure
+ * @return {@code true} if {@link Network} is valid and {@code soapEnvelope} is not null,
+ * {@code false} otherwise.
*/
- public SppResponseMessage exchangeSoapMessage(@NonNull SoapSerializationEnvelope soapEnvelope) {
+ public boolean exchangeSoapMessage(@NonNull SoapSerializationEnvelope soapEnvelope) {
if (mNetwork == null) {
Log.e(TAG, "Network is not established");
- return null;
+ return false;
+ }
+
+ if (mUrlConnection == null) {
+ Log.e(TAG, "Server certificate is not validated");
+ return false;
}
if (soapEnvelope == null) {
Log.e(TAG, "soapEnvelope is null");
- return null;
+ return false;
}
- if (mUrlConnection == null) {
- Log.e(TAG, "Server certificate is not validated");
- return null;
- }
+ mHandler.post(() -> performSoapMessageExchange(soapEnvelope));
+ return true;
+ }
+ private void performSoapMessageExchange(@NonNull SoapSerializationEnvelope soapEnvelope) {
if (mServiceConnection != null) {
mServiceConnection.disconnect();
}
@@ -205,21 +236,32 @@ public class OsuServerConnection {
mServiceConnection = getServiceConnection();
if (mServiceConnection == null) {
Log.e(TAG, "ServiceConnection for https is null");
- return null;
+ if (mOsuServerCallbacks != null) {
+ mOsuServerCallbacks.onReceivedSoapMessage(mOsuServerCallbacks.getSessionId(), null);
+ return;
+ }
}
- SppResponseMessage sppResponse;
+ SppResponseMessage sppResponse = null;
try {
// Sending the SOAP message
mHttpsTransport.call("", soapEnvelope);
Object response = soapEnvelope.bodyIn;
if (response == null) {
Log.e(TAG, "SoapObject is null");
- return null;
+ if (mOsuServerCallbacks != null) {
+ mOsuServerCallbacks.onReceivedSoapMessage(mOsuServerCallbacks.getSessionId(),
+ null);
+ return;
+ }
}
if (!(response instanceof SoapObject)) {
Log.e(TAG, "Not a SoapObject instance");
- return null;
+ if (mOsuServerCallbacks != null) {
+ mOsuServerCallbacks.onReceivedSoapMessage(mOsuServerCallbacks.getSessionId(),
+ null);
+ return;
+ }
}
SoapObject soapResponse = (SoapObject) response;
if (mVerboseLoggingEnabled) {
@@ -239,12 +281,19 @@ public class OsuServerConnection {
} else {
Log.e(TAG, "Failed to exchange the SOAP message");
}
- return null;
+ if (mOsuServerCallbacks != null) {
+ mOsuServerCallbacks.onReceivedSoapMessage(mOsuServerCallbacks.getSessionId(), null);
+ return;
+ }
} finally {
mServiceConnection.disconnect();
mServiceConnection = null;
}
- return sppResponse;
+
+ if (mOsuServerCallbacks != null) {
+ mOsuServerCallbacks.onReceivedSoapMessage(mOsuServerCallbacks.getSessionId(),
+ sppResponse);
+ }
}
/**
@@ -331,6 +380,7 @@ public class OsuServerConnection {
/**
* Returns the OSU certificate matching the FQDN of the OSU server
+ *
* @return {@link X509Certificate} OSU certificate matching FQDN of OSU server
*/
public X509Certificate getProviderCert() {
diff --git a/service/java/com/android/server/wifi/hotspot2/PasspointObjectFactory.java b/service/java/com/android/server/wifi/hotspot2/PasspointObjectFactory.java
index 953107cf7..f743882e3 100644
--- a/service/java/com/android/server/wifi/hotspot2/PasspointObjectFactory.java
+++ b/service/java/com/android/server/wifi/hotspot2/PasspointObjectFactory.java
@@ -130,7 +130,7 @@ public class PasspointObjectFactory{
* @return {@link OsuServerConnection}
*/
public OsuServerConnection makeOsuServerConnection() {
- return new OsuServerConnection();
+ return new OsuServerConnection(null);
}
diff --git a/service/java/com/android/server/wifi/hotspot2/PasspointProvisioner.java b/service/java/com/android/server/wifi/hotspot2/PasspointProvisioner.java
index 0c8a6a72e..d47f5eece 100644
--- a/service/java/com/android/server/wifi/hotspot2/PasspointProvisioner.java
+++ b/service/java/com/android/server/wifi/hotspot2/PasspointProvisioner.java
@@ -16,14 +16,18 @@
package com.android.server.wifi.hotspot2;
+import android.annotation.Nullable;
import android.content.Context;
+import android.content.Intent;
import android.net.Network;
+import android.net.wifi.WifiManager;
import android.net.wifi.hotspot2.IProvisioningCallback;
import android.net.wifi.hotspot2.OsuProvider;
import android.net.wifi.hotspot2.ProvisioningCallback;
import android.os.Handler;
import android.os.Looper;
import android.os.RemoteException;
+import android.os.UserHandle;
import android.util.Log;
import com.android.server.wifi.WifiNative;
@@ -51,6 +55,7 @@ public class PasspointProvisioner {
// TLS version to be used for HTTPS connection with OSU server
private static final String TLS_VERSION = "TLSv1";
+ private static final String OSU_APP_PACKAGE = "com.android.hotspot2";
private final Context mContext;
private final ProvisioningStateMachine mProvisioningStateMachine;
@@ -86,7 +91,7 @@ public class PasspointProvisioner {
mOsuNetworkConnection.init(mProvisioningStateMachine.getHandler());
// Offload the heavy load job to another thread
mProvisioningStateMachine.getHandler().post(() -> {
- mRedirectListener = RedirectListener.createInstance();
+ mRedirectListener = RedirectListener.createInstance(looper);
mWfaKeyStore.load();
mOsuServerConnection.init(mObjectFactory.getSSLContext(TLS_VERSION),
mObjectFactory.getTrustManagerImpl(mWfaKeyStore.get()));
@@ -140,6 +145,7 @@ public class PasspointProvisioner {
static final int STATE_OSU_AP_CONNECTED = 3;
static final int STATE_OSU_SERVER_CONNECTED = 4;
static final int STATE_WAITING_FOR_FIRST_SOAP_RESPONSE = 5;
+ static final int STATE_WAITING_FOR_REDIRECT_RESPONSE = 6;
private OsuProvider mOsuProvider;
private IProvisioningCallback mProvisioningCallback;
@@ -159,6 +165,7 @@ public class PasspointProvisioner {
/**
* Returns the handler on which a runnable can be posted
+ *
* @return Handler State Machine's handler
*/
public Handler getHandler() {
@@ -167,8 +174,10 @@ public class PasspointProvisioner {
/**
* Start Provisioning with the Osuprovider and invoke callbacks
+ *
* @param provider OsuProvider to provision with
* @param callback IProvisioningCallback to invoke callbacks on
+ * Note: Called on main thread (WifiService thread).
*/
public void startProvisioning(OsuProvider provider, IProvisioningCallback callback) {
if (mVerboseLoggingEnabled) {
@@ -198,6 +207,7 @@ public class PasspointProvisioner {
mServerUrl = serverUrl;
mProvisioningCallback = callback;
mOsuProvider = provider;
+
// Register for network and wifi state events during provisioning flow
mOsuNetworkConnection.setEventCallback(mOsuNetworkCallbacks);
@@ -216,6 +226,8 @@ public class PasspointProvisioner {
/**
* Handle Wifi Disable event
+ *
+ * Note: Called on main thread (WifiService thread).
*/
public void handleWifiDisabled() {
if (mVerboseLoggingEnabled) {
@@ -230,6 +242,8 @@ public class PasspointProvisioner {
/**
* Handle server validation failure
+ *
+ * Note: Called on main thread (WifiService thread).
*/
public void handleServerValidationFailure(int sessionId) {
if (mVerboseLoggingEnabled) {
@@ -249,6 +263,8 @@ public class PasspointProvisioner {
/**
* Handle status of server validation success
+ *
+ * Note: Called on main thread (WifiService thread).
*/
public void handleServerValidationSuccess(int sessionId) {
if (mVerboseLoggingEnabled) {
@@ -284,12 +300,6 @@ public class PasspointProvisioner {
}
invokeProvisioningCallback(PROVISIONING_STATUS,
ProvisioningCallback.OSU_STATUS_SERVICE_PROVIDER_VERIFIED);
-
- invokeProvisioningCallback(PROVISIONING_STATUS,
- ProvisioningCallback.OSU_STATUS_INIT_SOAP_EXCHANGE);
-
- // Move to initiate soap exchange
- changeState(STATE_WAITING_FOR_FIRST_SOAP_RESPONSE);
mProvisioningStateMachine.getHandler().post(() -> initSoapExchange());
}
@@ -301,71 +311,207 @@ public class PasspointProvisioner {
Log.v(TAG, "Initiates soap message exchange in state =" + mState);
}
- if (mState != STATE_WAITING_FOR_FIRST_SOAP_RESPONSE) {
+ if (mState != STATE_OSU_SERVER_CONNECTED) {
Log.e(TAG, "Initiates soap message exchange in wrong state=" + mState);
resetStateMachine(ProvisioningCallback.OSU_FAILURE_PROVISIONING_ABORTED);
return;
}
// Redirect uri used for signal of completion for registration process.
- final URL redirectUri = mRedirectListener.getURL();
- if (redirectUri == null) {
- Log.e(TAG, "redirectUri is not valid");
- resetStateMachine(ProvisioningCallback.OSU_FAILURE_PROVISIONING_ABORTED);
- return;
- }
+ final URL redirectUri = mRedirectListener.getServerUrl();
// Sending the first sppPostDevDataRequest message.
- SppResponseMessage sppResponse = mOsuServerConnection.exchangeSoapMessage(
+ if (mOsuServerConnection.exchangeSoapMessage(
PostDevDataMessage.serializeToSoapEnvelope(mContext, mSystemInfo,
redirectUri.toString(),
- SppConstants.SppReason.SUBSCRIPTION_REGISTRATION,
- null));
- if (sppResponse == null) {
- Log.e(TAG, "failed to send the sppPostDevData message");
+ SppConstants.SppReason.SUBSCRIPTION_REGISTRATION, null))) {
+ invokeProvisioningCallback(PROVISIONING_STATUS,
+ ProvisioningCallback.OSU_STATUS_INIT_SOAP_EXCHANGE);
+ // Move to initiate soap exchange
+ changeState(STATE_WAITING_FOR_FIRST_SOAP_RESPONSE);
+ } else {
+ Log.e(TAG, "HttpsConnection is not established for soap message exchange");
resetStateMachine(ProvisioningCallback.OSU_FAILURE_SOAP_MESSAGE_EXCHANGE);
return;
}
+ }
+
+ private void launchOsuWebView() {
+ if (mVerboseLoggingEnabled) {
+ Log.v(TAG, "launch Osu webview in state =" + mState);
+ }
- if (sppResponse.getMessageType()
- != SppResponseMessage.MessageType.POST_DEV_DATA_RESPONSE) {
- Log.e(TAG, "Expected a PostDevDataResponse, but got "
- + sppResponse.getMessageType());
- resetStateMachine(
- ProvisioningCallback.OSU_FAILURE_UNEXPECTED_SOAP_MESSAGE_TYPE);
+ if (mState != STATE_WAITING_FOR_FIRST_SOAP_RESPONSE) {
+ Log.e(TAG, "launch Osu webview in wrong state =" + mState);
+ resetStateMachine(ProvisioningCallback.OSU_FAILURE_PROVISIONING_ABORTED);
return;
}
- PostDevDataResponse devDataResponse = (PostDevDataResponse) sppResponse;
- mSessionId = devDataResponse.getSessionID();
- if (devDataResponse.getSppCommand().getExecCommandId()
- != SppCommand.ExecCommandId.BROWSER) {
- Log.e(TAG, "Expected a launchBrowser command, but got "
- + devDataResponse.getSppCommand().getExecCommandId());
- resetStateMachine(ProvisioningCallback.OSU_FAILURE_UNEXPECTED_COMMAND_TYPE);
+ // Start the redirect server to listen the HTTP redirect response from server
+ // as completion of user input.
+ if (!mRedirectListener.startServer(new RedirectListener.RedirectCallback() {
+ /** Called on different thread (RedirectListener thread). */
+ @Override
+ public void onRedirectReceived() {
+ if (mVerboseLoggingEnabled) {
+ Log.v(TAG, "Received HTTP redirect response");
+ }
+ mProvisioningStateMachine.getHandler().post(() -> handleRedirectResponse());
+ }
+
+ /** Called on main thread (WifiService thread). */
+ @Override
+ public void onRedirectTimedOut() {
+ if (mVerboseLoggingEnabled) {
+ Log.v(TAG, "Timed out to receive a HTTP redirect response");
+ }
+ mProvisioningStateMachine.handleTimeOutForRedirectResponse();
+ }
+ })) {
+ Log.e(TAG, "fails to start redirect listener");
+ resetStateMachine(ProvisioningCallback.OSU_FAILURE_START_REDIRECT_LISTENER);
return;
}
- Log.d(TAG, "Exec: " + devDataResponse.getSppCommand().getExecCommandId() + ", for '"
- + devDataResponse.getSppCommand().getCommandData() + "'");
+ Intent intent = new Intent(WifiManager.ACTION_PASSPOINT_LAUNCH_OSU_VIEW);
+ intent.setPackage(OSU_APP_PACKAGE);
+ intent.putExtra(WifiManager.EXTRA_OSU_NETWORK, mNetwork);
+ intent.putExtra(WifiManager.EXTRA_URL, mWebUrl);
- mWebUrl = ((BrowserUri) devDataResponse.getSppCommand().getCommandData()).getUri();
- if (mWebUrl == null) {
- Log.e(TAG, "No Web-Url");
- resetStateMachine(ProvisioningCallback.OSU_FAILURE_INVALID_SERVER_URL);
+ intent.setFlags(
+ Intent.FLAG_ACTIVITY_BROUGHT_TO_FRONT | Intent.FLAG_ACTIVITY_NEW_TASK);
+
+ // Verify that the intent will resolve to an activity
+ if (intent.resolveActivity(mContext.getPackageManager()) != null) {
+ mContext.startActivityAsUser(intent, UserHandle.CURRENT);
+ invokeProvisioningCallback(PROVISIONING_STATUS,
+ ProvisioningCallback.OSU_STATUS_WAITING_FOR_REDIRECT_RESPONSE);
+ changeState(STATE_WAITING_FOR_REDIRECT_RESPONSE);
+ } else {
+ Log.e(TAG, "can't resolve the activity for the intent");
+ resetStateMachine(ProvisioningCallback.OSU_FAILURE_NO_OSU_ACTIVITY_FOUND);
return;
}
+ }
+
+ /**
+ * Initiates the second SOAP message exchange with sending the sppPostDevData message.
+ */
+ private void secondSoapExchange() {
+ if (mVerboseLoggingEnabled) {
+ Log.v(TAG, "Initiates the second soap message exchange in state =" + mState);
+ }
- if (!mWebUrl.toLowerCase(Locale.US).contains(mSessionId.toLowerCase(Locale.US))) {
- Log.e(TAG, "Bad or Missing session ID in webUrl");
- resetStateMachine(ProvisioningCallback.OSU_FAILURE_INVALID_SERVER_URL);
+ if (mState != STATE_WAITING_FOR_REDIRECT_RESPONSE) {
+ Log.e(TAG, "Initiates the second soap message exchange in wrong state=" + mState);
+ resetStateMachine(ProvisioningCallback.OSU_FAILURE_PROVISIONING_ABORTED);
return;
}
+ // TODO(b/74244324): Implement a routine to transmit second SOAP message.
+ }
+
+ /**
+ * Handles SOAP message response sent by server
+ *
+ * @param sessionId indicating current session ID
+ * @param responseMessage SOAP SPP response, or {@code null} in any failure.
+ * Note: Called on main thread (WifiService thread).
+ */
+ public void handleSoapMessageResponse(int sessionId,
+ @Nullable SppResponseMessage responseMessage) {
+ if (sessionId != mCurrentSessionId) {
+ Log.w(TAG, "Expected soapMessageResponse callback for currentSessionId="
+ + mCurrentSessionId);
+ return;
+ }
+
+ if (responseMessage == null) {
+ Log.e(TAG, "failed to send the sppPostDevData message");
+ resetStateMachine(ProvisioningCallback.OSU_FAILURE_SOAP_MESSAGE_EXCHANGE);
+ return;
+ }
+
+ if (mState == STATE_WAITING_FOR_FIRST_SOAP_RESPONSE) {
+ if (responseMessage.getMessageType()
+ != SppResponseMessage.MessageType.POST_DEV_DATA_RESPONSE) {
+ Log.e(TAG, "Expected a PostDevDataResponse, but got "
+ + responseMessage.getMessageType());
+ resetStateMachine(
+ ProvisioningCallback.OSU_FAILURE_UNEXPECTED_SOAP_MESSAGE_TYPE);
+ return;
+ }
+
+ PostDevDataResponse devDataResponse = (PostDevDataResponse) responseMessage;
+ mSessionId = devDataResponse.getSessionID();
+ if (devDataResponse.getSppCommand().getExecCommandId()
+ != SppCommand.ExecCommandId.BROWSER) {
+ Log.e(TAG, "Expected a launchBrowser command, but got "
+ + devDataResponse.getSppCommand().getExecCommandId());
+ resetStateMachine(ProvisioningCallback.OSU_FAILURE_UNEXPECTED_COMMAND_TYPE);
+ return;
+ }
+
+ Log.d(TAG, "Exec: " + devDataResponse.getSppCommand().getExecCommandId() + ", for '"
+ + devDataResponse.getSppCommand().getCommandData() + "'");
+
+ mWebUrl = ((BrowserUri) devDataResponse.getSppCommand().getCommandData()).getUri();
+ if (mWebUrl == null) {
+ Log.e(TAG, "No Web-Url");
+ resetStateMachine(ProvisioningCallback.OSU_FAILURE_INVALID_SERVER_URL);
+ return;
+ }
+
+ if (!mWebUrl.toLowerCase(Locale.US).contains(mSessionId.toLowerCase(Locale.US))) {
+ Log.e(TAG, "Bad or Missing session ID in webUrl");
+ resetStateMachine(ProvisioningCallback.OSU_FAILURE_INVALID_SERVER_URL);
+ return;
+ }
+ launchOsuWebView();
+ }
+ }
+
+ /**
+ * Handles next step once receiving a HTTP redirect response.
+ *
+ * Note: Called on main thread (WifiService thread).
+ */
+ public void handleRedirectResponse() {
+ if (mState != STATE_WAITING_FOR_REDIRECT_RESPONSE) {
+ Log.e(TAG, "Received redirect request in wrong state=" + mState);
+ resetStateMachine(ProvisioningCallback.OSU_FAILURE_PROVISIONING_ABORTED);
+ return;
+ }
+
+ invokeProvisioningCallback(PROVISIONING_STATUS,
+ ProvisioningCallback.OSU_STATUS_REDIRECT_RESPONSE_RECEIVED);
+ mRedirectListener.stopServer();
+ secondSoapExchange();
+ }
+
+ /**
+ * Handles next step when timeout occurs because {@link RedirectListener} doesn't
+ * receive a HTTP redirect response.
+ *
+ * Note: Called on main thread (WifiService thread).
+ */
+ public void handleTimeOutForRedirectResponse() {
+ Log.e(TAG, "Timed out for HTTP redirect response");
+
+ if (mState != STATE_WAITING_FOR_REDIRECT_RESPONSE) {
+ Log.e(TAG, "Received timeout error for HTTP redirect response in wrong state="
+ + mState);
+ resetStateMachine(ProvisioningCallback.OSU_FAILURE_PROVISIONING_ABORTED);
+ return;
+ }
+ mRedirectListener.stopServer();
+ resetStateMachine(ProvisioningCallback.OSU_FAILURE_TIMED_OUT_REDIRECT_LISTENER);
}
/**
* Connected event received
+ *
* @param network Network object for this connection
+ * Note: Called on main thread (WifiService thread).
*/
public void handleConnectedEvent(Network network) {
if (mVerboseLoggingEnabled) {
@@ -402,6 +548,8 @@ public class PasspointProvisioner {
/**
* Disconnect event received
+ *
+ * Note: Called on main thread (WifiService thread).
*/
public void handleDisconnect() {
if (mVerboseLoggingEnabled) {
@@ -444,6 +592,7 @@ public class PasspointProvisioner {
private void resetStateMachine(int failureCode) {
invokeProvisioningCallback(PROVISIONING_FAILURE, failureCode);
+ mRedirectListener.stopServer();
mOsuNetworkConnection.setEventCallback(null);
mOsuNetworkConnection.disconnectIfNeeded();
mOsuServerConnection.setEventCallback(null);
@@ -454,6 +603,8 @@ public class PasspointProvisioner {
/**
* Callbacks for network and wifi events
+ *
+ * Note: Called on main thread (WifiService thread).
*/
class OsuNetworkCallbacks implements OsuNetworkConnection.Callbacks {
@@ -505,6 +656,8 @@ public class PasspointProvisioner {
/**
* Defines the callbacks expected from OsuServerConnection
+ *
+ * Note: Called on main thread (WifiService thread).
*/
public class OsuServerCallbacks {
private final int mSessionId;
@@ -515,6 +668,7 @@ public class PasspointProvisioner {
/**
* Returns the session ID corresponding to this callback
+ *
* @return int sessionID
*/
public int getSessionId() {
@@ -523,6 +677,7 @@ public class PasspointProvisioner {
/**
* Provides a server validation status for the session ID
+ *
* @param sessionId integer indicating current session ID
* @param succeeded boolean indicating success/failure of server validation
*/
@@ -541,8 +696,22 @@ public class PasspointProvisioner {
}
}
+ /**
+ * Callback when soap message is received from server.
+ *
+ * @param sessionId indicating current session ID
+ * @param responseMessage SOAP SPP response parsed or {@code null} in any failure
+ * Note: Called on different thread (OsuServer Thread)!
+ */
+ public void onReceivedSoapMessage(int sessionId,
+ @Nullable SppResponseMessage responseMessage) {
+ if (mVerboseLoggingEnabled) {
+ Log.v(TAG, "onReceivedSoapMessage with sessionId=" + sessionId);
+ }
+ mProvisioningStateMachine.getHandler().post(() ->
+ mProvisioningStateMachine.handleSoapMessageResponse(sessionId,
+ responseMessage));
+ }
}
}
-
-
diff --git a/service/java/com/android/server/wifi/hotspot2/soap/RedirectListener.java b/service/java/com/android/server/wifi/hotspot2/soap/RedirectListener.java
index 6b7873d3e..5c9391150 100644
--- a/service/java/com/android/server/wifi/hotspot2/soap/RedirectListener.java
+++ b/service/java/com/android/server/wifi/hotspot2/soap/RedirectListener.java
@@ -16,42 +16,91 @@
package com.android.server.wifi.hotspot2.soap;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.os.Looper;
import android.util.Log;
+import com.android.internal.annotations.VisibleForTesting;
+
import java.io.IOException;
import java.net.InetAddress;
import java.net.ServerSocket;
import java.net.URL;
import java.util.Random;
+import fi.iki.elonen.NanoHTTPD;
+
/**
- * Redirect message listener to listen for the redirect message from server.
+ * Server for listening for redirect request from the OSU server to indicate the completion
+ * of user input.
+ *
+ * A HTTP server will be started in the {@link RedirectListener#startServer} of {@link
+ * RedirectListener}, so the caller will need to invoke {@link RedirectListener#stop} once the
+ * redirect server no longer needed.
*/
-public class RedirectListener extends Thread {
+public class RedirectListener extends NanoHTTPD {
+ // 4 minutes for the maximum wait time.
+ @VisibleForTesting
+ static final int USER_TIMEOUT_MILLIS = 4 * 60 * 1000;
+
private static final String TAG = "RedirectListener";
- private final ServerSocket mServerSocket;
+
private final String mPath;
- private final URL mURL;
+ private final URL mServerUrl;
+ private final Handler mStartStopHandler;
+ private final Handler mHandler;
+ private Runnable mTimeOutTask;
+ private RedirectCallback mRedirectCallback;
+
+ /**
+ * Listener interface for handling redirect events.
+ */
+ public interface RedirectCallback {
+
+ /**
+ * Invoked when HTTP redirect response is received.
+ */
+ void onRedirectReceived();
+
+ /**
+ * Invoked when timeout occurs on receiving HTTP redirect response.
+ */
+ void onRedirectTimedOut();
+ }
+
+ @VisibleForTesting
+ /* package */ RedirectListener(Looper looper, @Nullable Looper startStopLooper, int port)
+ throws IOException {
+ super(InetAddress.getLocalHost().getHostAddress(), port);
- private RedirectListener() throws IOException {
- mServerSocket = new ServerSocket(0, 5, InetAddress.getLocalHost());
Random rnd = new Random(System.currentTimeMillis());
+
mPath = "rnd" + Integer.toString(Math.abs(rnd.nextInt()), Character.MAX_RADIX);
- mURL = new URL("http", mServerSocket.getInetAddress().getHostAddress(),
- mServerSocket.getLocalPort(), mPath);
- setName("HS20-Redirect-Listener");
- setDaemon(true);
+ mServerUrl = new URL("http", getHostname(), port, mPath);
+ mHandler = new Handler(looper);
+ mTimeOutTask = () -> mRedirectCallback.onRedirectTimedOut();
+ if (startStopLooper == null) {
+ HandlerThread redirectHandlerThread = new HandlerThread("RedirectListenerHandler");
+ redirectHandlerThread.start();
+ startStopLooper = redirectHandlerThread.getLooper();
+ }
+ mStartStopHandler = new Handler(startStopLooper);
}
/**
* Create an instance of {@link RedirectListener}
*
+ * @param looper Looper on which the {@link RedirectCallback} will be called.
* @return Instance of {@link RedirectListener}, {@code null} in any failure.
*/
- public static RedirectListener createInstance() {
+ public static RedirectListener createInstance(@NonNull Looper looper) {
RedirectListener redirectListener;
try {
- redirectListener = new RedirectListener();
+ redirectListener = new RedirectListener(looper, null,
+ new ServerSocket(0, 1, InetAddress.getLocalHost()).getLocalPort());
} catch (IOException e) {
Log.e(TAG, "fails to create an instance: " + e);
return null;
@@ -59,7 +108,73 @@ public class RedirectListener extends Thread {
return redirectListener;
}
- public URL getURL() {
- return mURL;
+ /**
+ * Start redirect listener
+ *
+ * @param callback to be notified when the redirect request is received or timed out.
+ * @return {@code true} in success, {@code false} if the {@code callback} is {@code null} or the
+ * server is already running.
+ */
+ public boolean startServer(@NonNull RedirectCallback callback) {
+ if (callback == null) {
+ return false;
+ }
+
+ if (isAlive()) {
+ Log.e(TAG, "redirect listener is already running");
+ return false;
+ }
+ mRedirectCallback = callback;
+
+ mStartStopHandler.post(() -> {
+ try {
+ start();
+ } catch (IOException e) {
+ Log.e(TAG, "unable to start redirect listener: " + e);
+ }
+ });
+ mHandler.postDelayed(mTimeOutTask, USER_TIMEOUT_MILLIS);
+ return true;
+ }
+
+ /**
+ * Stop redirect listener
+ */
+ public void stopServer() {
+ if (isServerAlive()) {
+ mStartStopHandler.post(() -> stop());
+ }
+ }
+
+ /**
+ * Check if the server is alive or not.
+ *
+ * @return {@code true} if the server is alive.
+ */
+ public boolean isServerAlive() {
+ return isAlive();
+ }
+
+ /**
+ * Get URL to which the local redirect server listens
+ *
+ * @return The URL for the local redirect server.
+ */
+ public URL getServerUrl() {
+ return mServerUrl;
+ }
+
+ @Override
+ public Response serve(IHTTPSession session) {
+
+ // Ignore all other requests except for a HTTP request that has the server url path with
+ // GET method.
+ if (session.getMethod() != Method.GET || !mServerUrl.getPath().equals(session.getUri())) {
+ return newFixedLengthResponse(Response.Status.NOT_FOUND, NanoHTTPD.MIME_HTML, "");
+ }
+
+ mHandler.removeCallbacks(mTimeOutTask);
+ mRedirectCallback.onRedirectReceived();
+ return newFixedLengthResponse("");
}
}
diff --git a/tests/wifitests/src/com/android/server/wifi/hotspot2/OsuServerConnectionTest.java b/tests/wifitests/src/com/android/server/wifi/hotspot2/OsuServerConnectionTest.java
index c91b1bcb5..8a123c2f2 100644
--- a/tests/wifitests/src/com/android/server/wifi/hotspot2/OsuServerConnectionTest.java
+++ b/tests/wifitests/src/com/android/server/wifi/hotspot2/OsuServerConnectionTest.java
@@ -16,9 +16,7 @@
package com.android.server.wifi.hotspot2;
-import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.ArgumentMatchers.isNull;
@@ -30,6 +28,7 @@ import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import android.net.Network;
+import android.os.test.TestLooper;
import android.support.test.filters.SmallTest;
import android.util.Pair;
@@ -78,6 +77,7 @@ public class OsuServerConnectionTest {
private static final int ENABLE_VERBOSE_LOGGING = 1;
private static final int TEST_SESSION_ID = 1;
+ private TestLooper mLooper = new TestLooper();
private OsuServerConnection mOsuServerConnection;
private URL mValidServerUrl;
private List<Pair<Locale, String>> mProviderIdentities = new ArrayList<>();
@@ -98,7 +98,7 @@ public class OsuServerConnectionTest {
@Before
public void setUp() throws Exception {
MockitoAnnotations.initMocks(this);
- mOsuServerConnection = new OsuServerConnection();
+ mOsuServerConnection = new OsuServerConnection(mLooper.getLooper());
mOsuServerConnection.enableVerboseLogging(ENABLE_VERBOSE_LOGGING);
mProviderIdentities.add(Pair.create(Locale.US, PROVIDER_NAME_VALID));
mValidServerUrl = new URL(TEST_VALID_URL);
@@ -249,16 +249,16 @@ public class OsuServerConnectionTest {
}
/**
- * Verifies {@code ExchangeSoapMessage} should return {@code null} if there is no connection.
+ * Verifies {@code ExchangeSoapMessage} should return {@code false} if there is no connection.
*/
@Test
public void verifyExchangeSoapMessageWithoutConnection() {
- assertNull(mOsuServerConnection.exchangeSoapMessage(
+ assertFalse(mOsuServerConnection.exchangeSoapMessage(
new SoapSerializationEnvelope(SoapEnvelope.VER12)));
}
/**
- * Verifies {@code ExchangeSoapMessage} should return {@code null} if {@code soapEnvelope} is
+ * Verifies {@code ExchangeSoapMessage} should return {@code false} if {@code soapEnvelope} is
* {@code null}
*/
@Test
@@ -267,12 +267,12 @@ public class OsuServerConnectionTest {
mOsuServerConnection.setEventCallback(mOsuServerCallbacks);
assertTrue(mOsuServerConnection.connect(mValidServerUrl, mNetwork));
- assertNull(mOsuServerConnection.exchangeSoapMessage(null));
+ assertFalse(mOsuServerConnection.exchangeSoapMessage(null));
}
/**
- * Verifies {@code ExchangeSoapMessage} should return {@code null} if exception occurs during
- * soap exchange.
+ * Verifies {@code ExchangeSoapMessage} should get {@code null} message if exception occurs
+ * during soap exchange.
*/
@Test
public void verifyExchangeSoapMessageWithException() throws Exception {
@@ -280,14 +280,19 @@ public class OsuServerConnectionTest {
MockitoSession session = ExtendedMockito.mockitoSession().mockStatic(
HttpsTransport.class).startMocking();
try {
+ mOsuServerConnection.init(mTlsContext, mDelegate);
+ mOsuServerConnection.setEventCallback(mOsuServerCallbacks);
when(HttpsTransport.createInstance(any(Network.class), any(URL.class))).thenReturn(
mHttpsTransport);
doThrow(new IOException()).when(mHttpsTransport).call(any(String.class),
any(SoapSerializationEnvelope.class));
assertTrue(mOsuServerConnection.connect(mValidServerUrl, mNetwork));
- assertNull(mOsuServerConnection.exchangeSoapMessage(
+ assertTrue(mOsuServerConnection.exchangeSoapMessage(
new SoapSerializationEnvelope(SoapEnvelope.VER12)));
+
+ mLooper.dispatchAll();
+ verify(mOsuServerCallbacks).onReceivedSoapMessage(anyInt(), isNull());
} finally {
session.finishMocking();
}
@@ -302,6 +307,8 @@ public class OsuServerConnectionTest {
MockitoSession session = ExtendedMockito.mockitoSession().mockStatic(
HttpsTransport.class).startMocking();
try {
+ mOsuServerConnection.init(mTlsContext, mDelegate);
+ mOsuServerConnection.setEventCallback(mOsuServerCallbacks);
when(HttpsTransport.createInstance(any(Network.class), any(URL.class))).thenReturn(
mHttpsTransport);
assertTrue(mOsuServerConnection.connect(mValidServerUrl, mNetwork));
@@ -314,7 +321,10 @@ public class OsuServerConnectionTest {
envelope.bodyIn = new SoapObject();
when(SoapParser.getResponse(any(SoapObject.class))).thenReturn(mSppResponseMessage);
- assertEquals(mSppResponseMessage, mOsuServerConnection.exchangeSoapMessage(envelope));
+ assertTrue(mOsuServerConnection.exchangeSoapMessage(envelope));
+
+ mLooper.dispatchAll();
+ verify(mOsuServerCallbacks).onReceivedSoapMessage(anyInt(), eq(mSppResponseMessage));
} finally {
session.finishMocking();
}
diff --git a/tests/wifitests/src/com/android/server/wifi/hotspot2/PasspointProvisionerTest.java b/tests/wifitests/src/com/android/server/wifi/hotspot2/PasspointProvisionerTest.java
index 161bd2a86..5e73cd652 100644
--- a/tests/wifitests/src/com/android/server/wifi/hotspot2/PasspointProvisionerTest.java
+++ b/tests/wifitests/src/com/android/server/wifi/hotspot2/PasspointProvisionerTest.java
@@ -29,6 +29,11 @@ import static org.mockito.Mockito.verifyNoMoreInteractions;
import static org.mockito.Mockito.when;
import android.content.Context;
+import android.content.Intent;
+import android.content.pm.ActivityInfo;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
import android.net.Network;
import android.net.wifi.WifiManager;
import android.net.wifi.WifiSsid;
@@ -42,19 +47,23 @@ import android.os.test.TestLooper;
import android.support.test.filters.SmallTest;
import android.telephony.TelephonyManager;
+import com.android.dx.mockito.inline.extended.ExtendedMockito;
import com.android.org.conscrypt.TrustManagerImpl;
import com.android.server.wifi.WifiNative;
import com.android.server.wifi.hotspot2.soap.PostDevDataResponse;
+import com.android.server.wifi.hotspot2.soap.RedirectListener;
import com.android.server.wifi.hotspot2.soap.SppResponseMessage;
import com.android.server.wifi.hotspot2.soap.command.BrowserUri;
import com.android.server.wifi.hotspot2.soap.command.SppCommand;
+import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.ksoap2.serialization.SoapSerializationEnvelope;
import org.mockito.ArgumentCaptor;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
+import org.mockito.MockitoSession;
import java.net.URL;
import java.security.KeyStore;
@@ -71,6 +80,7 @@ public class PasspointProvisionerTest {
private static final int STEP_INIT = 0;
private static final int STEP_AP_CONNECT = 1;
private static final int STEP_SERVER_CONNECT = 2;
+ private static final int STEP_WAIT_FOR_REDIRECT_RESPONSE = 3;
private static final String TEST_DEV_ID = "12312341";
private static final String TEST_MANUFACTURER = Build.MANUFACTURER;
@@ -84,20 +94,27 @@ public class PasspointProvisionerTest {
private static final String TEST_SW_VERSION = "Android Test 1.0";
private static final String TEST_FW_VERSION = "Test FW 1.0";
private static final String TEST_REDIRECT_URL = "http://127.0.0.1:12345/index.htm";
+ private static final String OSU_APP_PACKAGE = "com.android.hotspot2";
+ private static final String OSU_APP_NAME = "OsuLogin";
private PasspointProvisioner mPasspointProvisioner;
private TestLooper mLooper = new TestLooper();
private Handler mHandler;
private OsuNetworkConnection.Callbacks mOsuNetworkCallbacks;
private PasspointProvisioner.OsuServerCallbacks mOsuServerCallbacks;
+ private RedirectListener.RedirectCallback mRedirectReceivedListener;
private ArgumentCaptor<OsuNetworkConnection.Callbacks> mOsuNetworkCallbacksCaptor =
ArgumentCaptor.forClass(OsuNetworkConnection.Callbacks.class);
private ArgumentCaptor<PasspointProvisioner.OsuServerCallbacks> mOsuServerCallbacksCaptor =
ArgumentCaptor.forClass(PasspointProvisioner.OsuServerCallbacks.class);
+ private ArgumentCaptor<RedirectListener.RedirectCallback>
+ mOnRedirectReceivedArgumentCaptor =
+ ArgumentCaptor.forClass(RedirectListener.RedirectCallback.class);
private ArgumentCaptor<Handler> mHandlerCaptor = ArgumentCaptor.forClass(Handler.class);
private OsuProvider mOsuProvider;
private TrustManagerImpl mDelegate;
private URL mTestUrl;
+ private MockitoSession mSession;
@Mock PasspointObjectFactory mObjectFactory;
@Mock Context mContext;
@@ -110,17 +127,27 @@ public class PasspointProvisionerTest {
@Mock KeyStore mKeyStore;
@Mock SSLContext mTlsContext;
@Mock WifiNative mWifiNative;
- @Mock SoapSerializationEnvelope mSoapEnvelope;
@Mock PostDevDataResponse mSppResponseMessage;
@Mock SystemInfo mSystemInfo;
@Mock TelephonyManager mTelephonyManager;
@Mock SppCommand mSppCommand;
@Mock BrowserUri mBrowserUri;
+ @Mock RedirectListener mRedirectListener;
+ @Mock PackageManager mPackageManager;
@Before
public void setUp() throws Exception {
MockitoAnnotations.initMocks(this);
mTestUrl = new URL(TEST_REDIRECT_URL);
+ mSession = ExtendedMockito.mockitoSession().mockStatic(
+ RedirectListener.class).startMocking();
+
+ when(RedirectListener.createInstance(mLooper.getLooper())).thenReturn(
+ mRedirectListener);
+ when(mRedirectListener.getServerUrl()).thenReturn(new URL(TEST_REDIRECT_URL));
+ when(mRedirectListener.startServer(
+ any(RedirectListener.RedirectCallback.class))).thenReturn(true);
+ when(mRedirectListener.isAlive()).thenReturn(true);
when(mWifiManager.isWifiEnabled()).thenReturn(true);
when(mObjectFactory.makeOsuNetworkConnection(any(Context.class)))
.thenReturn(mOsuNetworkConnection);
@@ -162,8 +189,20 @@ public class PasspointProvisionerTest {
when(mSppCommand.getCommandData()).thenReturn(mBrowserUri);
when(mBrowserUri.getUri()).thenReturn(TEST_URL);
when(mOsuServerConnection.exchangeSoapMessage(
- any(SoapSerializationEnvelope.class))).thenReturn(
- mSppResponseMessage);
+ any(SoapSerializationEnvelope.class))).thenReturn(true);
+ when(mContext.getPackageManager()).thenReturn(mPackageManager);
+ ResolveInfo resolveInfo = new ResolveInfo();
+ resolveInfo.activityInfo = new ActivityInfo();
+ resolveInfo.activityInfo.applicationInfo = new ApplicationInfo();
+ resolveInfo.activityInfo.name = OSU_APP_NAME;
+ resolveInfo.activityInfo.applicationInfo.packageName = OSU_APP_PACKAGE;
+ when(mPackageManager.resolveActivity(any(Intent.class),
+ eq(PackageManager.MATCH_DEFAULT_ONLY))).thenReturn(resolveInfo);
+ }
+
+ @After
+ public void cleanUp() {
+ mSession.finishMocking();
}
private void initAndStartProvisioning() {
@@ -205,6 +244,30 @@ public class PasspointProvisionerTest {
} else if (step == STEP_SERVER_CONNECT) {
verify(mCallback).onProvisioningStatus(
ProvisioningCallback.OSU_STATUS_SERVER_CONNECTED);
+ } else if (step == STEP_WAIT_FOR_REDIRECT_RESPONSE) {
+ // Server validation passed
+ mOsuServerCallbacks.onServerValidationStatus(mOsuServerCallbacks.getSessionId(),
+ true);
+ mLooper.dispatchAll();
+
+ verify(mCallback).onProvisioningStatus(
+ ProvisioningCallback.OSU_STATUS_SERVER_VALIDATED);
+ verify(mCallback).onProvisioningStatus(
+ ProvisioningCallback.OSU_STATUS_SERVICE_PROVIDER_VERIFIED);
+ verify(mCallback).onProvisioningStatus(
+ ProvisioningCallback.OSU_STATUS_INIT_SOAP_EXCHANGE);
+
+ // Received soapMessageResponse
+ mOsuServerCallbacks.onReceivedSoapMessage(mOsuServerCallbacks.getSessionId(),
+ mSppResponseMessage);
+ mLooper.dispatchAll();
+
+ verify(mCallback).onProvisioningStatus(
+ ProvisioningCallback.OSU_STATUS_WAITING_FOR_REDIRECT_RESPONSE);
+ verify(mRedirectListener, atLeastOnce())
+ .startServer(mOnRedirectReceivedArgumentCaptor.capture());
+ mRedirectReceivedListener = mOnRedirectReceivedArgumentCaptor.getValue();
+ verifyNoMoreInteractions(mCallback);
}
}
}
@@ -426,7 +489,7 @@ public class PasspointProvisionerTest {
public void verifyExchangingSoapMessageFailure() throws RemoteException {
// Fail to exchange the SOAP message
when(mOsuServerConnection.exchangeSoapMessage(
- any(SoapSerializationEnvelope.class))).thenReturn(null);
+ any(SoapSerializationEnvelope.class))).thenReturn(false);
stopAfterStep(STEP_SERVER_CONNECT);
// Server validation passed
@@ -436,19 +499,21 @@ public class PasspointProvisionerTest {
verify(mCallback).onProvisioningStatus(ProvisioningCallback.OSU_STATUS_SERVER_VALIDATED);
verify(mCallback).onProvisioningStatus(
ProvisioningCallback.OSU_STATUS_SERVICE_PROVIDER_VERIFIED);
- verify(mCallback).onProvisioningStatus(ProvisioningCallback.OSU_STATUS_INIT_SOAP_EXCHANGE);
verify(mCallback).onProvisioningFailure(
ProvisioningCallback.OSU_FAILURE_SOAP_MESSAGE_EXCHANGE);
- // Osu provider verification is the last current step in the flow, no more runnables posted.
+ // No further runnables posted
verifyNoMoreInteractions(mCallback);
}
/**
- * Verifies that the right provisioning callbacks are invoked as the provisioner progresses
- * to the end as successful case.
+ * Verifies that the right provisioning callbacks are invoked when there is no OSU activity for
+ * the intent
*/
@Test
- public void verifyProvisioningFlowForSuccessfulCase() throws RemoteException {
+ public void verifyNoOsuActivityFoundFailure() throws RemoteException {
+ // There is no activity found for the intent
+ when(mPackageManager.resolveActivity(any(Intent.class),
+ eq(PackageManager.MATCH_DEFAULT_ONLY))).thenReturn(null);
stopAfterStep(STEP_SERVER_CONNECT);
// Server validation passed
@@ -459,8 +524,53 @@ public class PasspointProvisionerTest {
verify(mCallback).onProvisioningStatus(
ProvisioningCallback.OSU_STATUS_SERVICE_PROVIDER_VERIFIED);
verify(mCallback).onProvisioningStatus(ProvisioningCallback.OSU_STATUS_INIT_SOAP_EXCHANGE);
- // Osu provider verification is the last current step in the flow, no more runnables posted.
+
+ // Received soapMessageResponse
+ mOsuServerCallbacks.onReceivedSoapMessage(mOsuServerCallbacks.getSessionId(),
+ mSppResponseMessage);
+ mLooper.dispatchAll();
+
+ verify(mCallback).onProvisioningFailure(
+ ProvisioningCallback.OSU_FAILURE_NO_OSU_ACTIVITY_FOUND);
+ // No further runnables posted
verifyNoMoreInteractions(mCallback);
}
-}
+ /**
+ * Verifies that the right provisioning callbacks are invoked when timeout occurs for HTTP
+ * redirect response.
+ */
+ @Test
+ public void verifyRedirectResponseTimeout() throws RemoteException {
+ stopAfterStep(STEP_WAIT_FOR_REDIRECT_RESPONSE);
+
+ // Timed out for HTTP redirect response.
+ mRedirectReceivedListener.onRedirectTimedOut();
+ mLooper.dispatchAll();
+
+ verify(mRedirectListener, atLeastOnce()).stopServer();
+ verify(mCallback).onProvisioningFailure(
+ ProvisioningCallback.OSU_FAILURE_TIMED_OUT_REDIRECT_LISTENER);
+ // No further runnables posted
+ verifyNoMoreInteractions(mCallback);
+ }
+
+ /**
+ * Verifies that the right provisioning callbacks are invoked as the provisioner progresses
+ * to the end as successful case.
+ */
+ @Test
+ public void verifyProvisioningFlowForSuccessfulCase() throws RemoteException {
+ stopAfterStep(STEP_WAIT_FOR_REDIRECT_RESPONSE);
+
+ // Received HTTP redirect response.
+ mRedirectReceivedListener.onRedirectReceived();
+ mLooper.dispatchAll();
+
+ verify(mRedirectListener, atLeastOnce()).stopServer();
+ verify(mCallback).onProvisioningStatus(
+ ProvisioningCallback.OSU_STATUS_REDIRECT_RESPONSE_RECEIVED);
+ // No further runnables posted
+ verifyNoMoreInteractions(mCallback);
+ }
+}
diff --git a/tests/wifitests/src/com/android/server/wifi/hotspot2/soap/PostDevDataMessageTest.java b/tests/wifitests/src/com/android/server/wifi/hotspot2/soap/PostDevDataMessageTest.java
index de95cc8ca..179ac68a6 100644
--- a/tests/wifitests/src/com/android/server/wifi/hotspot2/soap/PostDevDataMessageTest.java
+++ b/tests/wifitests/src/com/android/server/wifi/hotspot2/soap/PostDevDataMessageTest.java
@@ -43,7 +43,6 @@ import org.ksoap2.serialization.SoapPrimitive;
import org.ksoap2.serialization.SoapSerializationEnvelope;
import org.mockito.Mock;
-
/**
* Unit tests for {@link PostDevDataMessage}.
*/
diff --git a/tests/wifitests/src/com/android/server/wifi/hotspot2/soap/PostDevDataResponseTest.java b/tests/wifitests/src/com/android/server/wifi/hotspot2/soap/PostDevDataResponseTest.java
index 25e91cba8..d5b827d8f 100644
--- a/tests/wifitests/src/com/android/server/wifi/hotspot2/soap/PostDevDataResponseTest.java
+++ b/tests/wifitests/src/com/android/server/wifi/hotspot2/soap/PostDevDataResponseTest.java
@@ -20,6 +20,8 @@ import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNull;
import static org.mockito.MockitoAnnotations.initMocks;
+import android.support.test.filters.SmallTest;
+
import com.android.server.wifi.hotspot2.soap.command.SppCommand;
import org.junit.Before;
@@ -30,6 +32,7 @@ import org.ksoap2.serialization.SoapObject;
/**
* Unit tests for {@link PostDevDataResponse}.
*/
+@SmallTest
public class PostDevDataResponseTest {
private static final String EXEC = "exec";
private static final String BROWSER_COMMAND = "launchBrowserToURI";
diff --git a/tests/wifitests/src/com/android/server/wifi/hotspot2/soap/RedirectListenerTest.java b/tests/wifitests/src/com/android/server/wifi/hotspot2/soap/RedirectListenerTest.java
new file mode 100644
index 000000000..c4fb3fd71
--- /dev/null
+++ b/tests/wifitests/src/com/android/server/wifi/hotspot2/soap/RedirectListenerTest.java
@@ -0,0 +1,172 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wifi.hotspot2.soap;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+import static org.mockito.MockitoAnnotations.initMocks;
+
+import android.os.Looper;
+import android.os.test.TestLooper;
+import android.support.test.filters.SmallTest;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.Mock;
+
+import java.io.IOException;
+import java.net.URL;
+
+import fi.iki.elonen.NanoHTTPD;
+
+/**
+ * Unit tests for {@link RedirectListener}.
+ */
+@SmallTest
+public class RedirectListenerTest {
+ private static final int TEST_PORT = 1010;
+
+ private RedirectListenerSpy mRedirectListener;
+ private URL mServerUrl;
+ private TestLooper mLooper = new TestLooper();
+
+ @Mock RedirectListener.RedirectCallback mListener;
+ @Mock NanoHTTPD.IHTTPSession mIHTTPSession;
+
+ /** Spy class to avoid start/stop {@link NanoHTTPD} server */
+ private class RedirectListenerSpy extends RedirectListener {
+ boolean mIsStart = false;
+ RedirectListenerSpy(Looper looper, int port) throws IOException {
+ super(looper, looper, port);
+ }
+
+ @Override
+ public void start() {
+ mIsStart = true;
+ }
+
+ @Override
+ public void stop() {
+ mIsStart = false;
+ }
+
+ @Override
+ public boolean isServerAlive() {
+ return mIsStart;
+ }
+ }
+
+ /**
+ * Sets up test.
+ */
+ @Before
+ public void setUp() throws Exception {
+ initMocks(this);
+
+ mRedirectListener = new RedirectListenerSpy(mLooper.getLooper(), TEST_PORT);
+ mServerUrl = mRedirectListener.getServerUrl();
+ }
+
+ private void verifyStartServer() {
+ mRedirectListener.startServer(mListener);
+ mLooper.dispatchAll();
+
+ assertTrue(mRedirectListener.mIsStart);
+ }
+
+ private void verifyStopServer() {
+ mRedirectListener.stopServer();
+ mLooper.dispatchAll();
+
+ assertFalse(mRedirectListener.mIsStart);
+ }
+
+ /**
+ * Verifies that Timeout handler will be invoked when There is no a known GET request received
+ * in a {@link RedirectListener#USER_TIMEOUT_MILLIS}.
+ */
+ @Test
+ public void timeOutForKnownGetRequest() {
+ when(mIHTTPSession.getMethod()).thenReturn(NanoHTTPD.Method.PUT);
+ verifyStartServer();
+ mRedirectListener.serve(mIHTTPSession);
+
+ verify(mListener, never()).onRedirectReceived();
+
+ // Timeout has expired.
+ mLooper.moveTimeForward(RedirectListener.USER_TIMEOUT_MILLIS);
+ mLooper.dispatchAll();
+
+ verify(mListener).onRedirectTimedOut();
+ verifyStopServer();
+ }
+
+ /**
+ * Verifies that {@link RedirectListener.RedirectCallback#onRedirectReceived()} will not be
+ * invoked when receiving a GET request with an unexpected path.
+ */
+ @Test
+ public void receiveUnknownGetRequest() {
+ when(mIHTTPSession.getMethod()).thenReturn(NanoHTTPD.Method.GET);
+ when(mIHTTPSession.getUri()).thenReturn("/test");
+ verifyStartServer();
+
+ mRedirectListener.serve(mIHTTPSession);
+
+ verify(mListener, never()).onRedirectReceived();
+
+ // Timeout has expired.
+ mLooper.moveTimeForward(RedirectListener.USER_TIMEOUT_MILLIS);
+ mLooper.dispatchAll();
+
+ verify(mListener).onRedirectTimedOut();
+ verifyStopServer();
+ }
+
+ /**
+ * Verifies that a {@link RedirectListener.RedirectCallback#onRedirectReceived()} callback will
+ * be invoked when receiving a GET request with an expected path.
+ */
+ @Test
+ public void receiveKnownGetRequest() {
+ when(mIHTTPSession.getMethod()).thenReturn(NanoHTTPD.Method.GET);
+ when(mIHTTPSession.getUri()).thenReturn(mServerUrl.getPath());
+ verifyStartServer();
+
+ mRedirectListener.serve(mIHTTPSession);
+
+ verify(mListener).onRedirectReceived();
+
+ mLooper.moveTimeForward(RedirectListener.USER_TIMEOUT_MILLIS);
+ mLooper.dispatchAll();
+
+ // TimeoutTask is cancelled once receiving HTTP redirect response.
+ verify(mListener, never()).onRedirectTimedOut();
+ verifyStopServer();
+ }
+}
+
+
+
+
+
+
+
+
diff --git a/tests/wifitests/src/com/android/server/wifi/hotspot2/soap/SoapParserTest.java b/tests/wifitests/src/com/android/server/wifi/hotspot2/soap/SoapParserTest.java
index 344d1f93f..3bb63a157 100644
--- a/tests/wifitests/src/com/android/server/wifi/hotspot2/soap/SoapParserTest.java
+++ b/tests/wifitests/src/com/android/server/wifi/hotspot2/soap/SoapParserTest.java
@@ -20,6 +20,8 @@ import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import static org.mockito.MockitoAnnotations.initMocks;
+import android.support.test.filters.SmallTest;
+
import org.junit.Before;
import org.junit.Test;
import org.ksoap2.serialization.PropertyInfo;
@@ -28,6 +30,7 @@ import org.ksoap2.serialization.SoapObject;
/**
* Unit tests for {@link SoapParser}.
*/
+@SmallTest
public class SoapParserTest {
private static final String EXEC = "exec";
private static final String BROWSER_COMMAND = "launchBrowserToURI";
diff --git a/tests/wifitests/src/com/android/server/wifi/hotspot2/soap/SppResponseMessageTest.java b/tests/wifitests/src/com/android/server/wifi/hotspot2/soap/SppResponseMessageTest.java
index 5253946b2..5e6ed070c 100644
--- a/tests/wifitests/src/com/android/server/wifi/hotspot2/soap/SppResponseMessageTest.java
+++ b/tests/wifitests/src/com/android/server/wifi/hotspot2/soap/SppResponseMessageTest.java
@@ -20,6 +20,8 @@ import static org.junit.Assert.assertEquals;
import static org.junit.Assert.fail;
import static org.mockito.MockitoAnnotations.initMocks;
+import android.support.test.filters.SmallTest;
+
import org.junit.Before;
import org.junit.Test;
import org.ksoap2.serialization.SoapObject;
@@ -30,6 +32,7 @@ import java.util.Map;
/**
* Unit tests for {@link SppResponseMessage}.
*/
+@SmallTest
public class SppResponseMessageTest {
private static final String TEST_STATUS = "OK";
private static final String TEST_ERROR_STATUS = "Error occurred";