summaryrefslogtreecommitdiff
path: root/service
diff options
context:
space:
mode:
authorTreeHugger Robot <treehugger-gerrit@google.com>2018-10-05 19:38:42 +0000
committerAndroid (Google) Code Review <android-gerrit@google.com>2018-10-05 19:38:42 +0000
commit574dad6518cec201bab9cee3942995094e4a6b40 (patch)
treea78aae9c5f86ed8429f62e5f614b8b0ef2ca2739 /service
parent8ef190b8523924a392fabb5d1cc5a880908a9a3a (diff)
parent63d022cb4aae028e21902cc6839875bc8c091aca (diff)
Merge "passpoint-r2: sending a second SOAP message and receiving a response"
Diffstat (limited to 'service')
-rw-r--r--service/java/com/android/server/wifi/hotspot2/OsuServerConnection.java25
-rw-r--r--service/java/com/android/server/wifi/hotspot2/PasspointProvisioner.java388
-rw-r--r--service/java/com/android/server/wifi/hotspot2/soap/command/PpsMoData.java127
-rw-r--r--service/java/com/android/server/wifi/hotspot2/soap/command/SppCommand.java1
4 files changed, 366 insertions, 175 deletions
diff --git a/service/java/com/android/server/wifi/hotspot2/OsuServerConnection.java b/service/java/com/android/server/wifi/hotspot2/OsuServerConnection.java
index fd993e599..9952ce43b 100644
--- a/service/java/com/android/server/wifi/hotspot2/OsuServerConnection.java
+++ b/service/java/com/android/server/wifi/hotspot2/OsuServerConnection.java
@@ -162,7 +162,6 @@ public class OsuServerConnection {
return false;
}
mUrlConnection = urlConnection;
- mHttpsTransport = HttpsTransport.createInstance(network, url);
return true;
}
@@ -233,13 +232,13 @@ public class OsuServerConnection {
mServiceConnection.disconnect();
}
- mServiceConnection = getServiceConnection();
+ mServiceConnection = getServiceConnection(mUrl, mNetwork);
if (mServiceConnection == null) {
Log.e(TAG, "ServiceConnection for https is null");
if (mOsuServerCallbacks != null) {
mOsuServerCallbacks.onReceivedSoapMessage(mOsuServerCallbacks.getSessionId(), null);
- return;
}
+ return;
}
SppResponseMessage sppResponse = null;
@@ -252,16 +251,16 @@ public class OsuServerConnection {
if (mOsuServerCallbacks != null) {
mOsuServerCallbacks.onReceivedSoapMessage(mOsuServerCallbacks.getSessionId(),
null);
- return;
}
+ return;
}
if (!(response instanceof SoapObject)) {
Log.e(TAG, "Not a SoapObject instance");
if (mOsuServerCallbacks != null) {
mOsuServerCallbacks.onReceivedSoapMessage(mOsuServerCallbacks.getSessionId(),
null);
- return;
}
+ return;
}
SoapObject soapResponse = (SoapObject) response;
if (mVerboseLoggingEnabled) {
@@ -277,19 +276,18 @@ public class OsuServerConnection {
sppResponse = SoapParser.getResponse(soapResponse);
} catch (Exception e) {
if (e instanceof SSLHandshakeException) {
- Log.e(TAG, "Failed to make TLS connection");
+ Log.e(TAG, "Failed to make TLS connection: " + e);
} else {
- Log.e(TAG, "Failed to exchange the SOAP message");
+ Log.e(TAG, "Failed to exchange the SOAP message: " + e);
}
if (mOsuServerCallbacks != null) {
mOsuServerCallbacks.onReceivedSoapMessage(mOsuServerCallbacks.getSessionId(), null);
- return;
}
+ return;
} finally {
mServiceConnection.disconnect();
mServiceConnection = null;
}
-
if (mOsuServerCallbacks != null) {
mOsuServerCallbacks.onReceivedSoapMessage(mOsuServerCallbacks.getSessionId(),
sppResponse);
@@ -301,9 +299,12 @@ public class OsuServerConnection {
*
* @return {@link HttpsServiceConnection}
*/
- private HttpsServiceConnection getServiceConnection() {
+ private HttpsServiceConnection getServiceConnection(@NonNull URL url,
+ @NonNull Network network) {
HttpsServiceConnection serviceConnection;
try {
+ // Creates new HTTPS connection.
+ mHttpsTransport = HttpsTransport.createInstance(network, url);
serviceConnection = (HttpsServiceConnection) mHttpsTransport.getServiceConnection();
if (serviceConnection != null) {
serviceConnection.setSSLSocketFactory(mSocketFactory);
@@ -365,8 +366,8 @@ public class OsuServerConnection {
}
}
if (mOsuServerCallbacks != null) {
- mOsuServerCallbacks.onServerValidationStatus(
- mOsuServerCallbacks.getSessionId(), certsValid);
+ mOsuServerCallbacks.onServerValidationStatus(mOsuServerCallbacks.getSessionId(),
+ certsValid);
}
}
diff --git a/service/java/com/android/server/wifi/hotspot2/PasspointProvisioner.java b/service/java/com/android/server/wifi/hotspot2/PasspointProvisioner.java
index d47f5eece..825ffe388 100644
--- a/service/java/com/android/server/wifi/hotspot2/PasspointProvisioner.java
+++ b/service/java/com/android/server/wifi/hotspot2/PasspointProvisioner.java
@@ -16,6 +16,7 @@
package com.android.server.wifi.hotspot2;
+import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.Context;
import android.content.Intent;
@@ -23,7 +24,9 @@ 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.PasspointConfiguration;
import android.net.wifi.hotspot2.ProvisioningCallback;
+import android.net.wifi.hotspot2.omadm.PpsMoParser;
import android.os.Handler;
import android.os.Looper;
import android.os.RemoteException;
@@ -37,6 +40,7 @@ import com.android.server.wifi.hotspot2.soap.RedirectListener;
import com.android.server.wifi.hotspot2.soap.SppConstants;
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.PpsMoData;
import com.android.server.wifi.hotspot2.soap.command.SppCommand;
import java.net.MalformedURLException;
@@ -146,6 +150,7 @@ public class PasspointProvisioner {
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;
+ static final int STATE_WAITING_FOR_SECOND_SOAP_RESPONSE = 7;
private OsuProvider mOsuProvider;
private IProvisioningCallback mProvisioningCallback;
@@ -285,6 +290,207 @@ public class PasspointProvisioner {
}
/**
+ * 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) {
+ Log.v(TAG, "Connected event received in state=" + mState);
+ }
+ if (mState != STATE_WAITING_TO_CONNECT) {
+ // Not waiting for a connection
+ Log.wtf(TAG, "Connection event unhandled in state=" + mState);
+ return;
+ }
+ invokeProvisioningCallback(PROVISIONING_STATUS,
+ ProvisioningCallback.OSU_STATUS_AP_CONNECTED);
+ changeState(STATE_OSU_AP_CONNECTED);
+ initiateServerConnection(network);
+ }
+
+ /**
+ * 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();
+ } else if (mState == STATE_WAITING_FOR_SECOND_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;
+ if (devDataResponse.getSppCommand() == null
+ || devDataResponse.getSppCommand().getSppCommandId()
+ != SppCommand.CommandId.ADD_MO) {
+ Log.e(TAG, "Expected a ADD_MO command, but got " + (
+ (devDataResponse.getSppCommand() == null) ? "null"
+ : devDataResponse.getSppCommand().getSppCommandId()));
+ resetStateMachine(
+ ProvisioningCallback.OSU_FAILURE_UNEXPECTED_COMMAND_TYPE);
+ return;
+ }
+
+ PasspointConfiguration passpointConfig = buildPasspointConfiguration(
+ (PpsMoData) devDataResponse.getSppCommand().getCommandData());
+
+ // TODO(b/74244324): Implement a routine to transmit third SOAP message.
+ } else {
+ if (mVerboseLoggingEnabled) {
+ Log.v(TAG, "Received an unexpected SOAP message in state=" + mState);
+ }
+ }
+ }
+
+ /**
+ * Disconnect event received
+ *
+ * Note: Called on main thread (WifiService thread).
+ */
+ public void handleDisconnect() {
+ if (mVerboseLoggingEnabled) {
+ Log.v(TAG, "Connection failed in state=" + mState);
+ }
+ if (mState == STATE_INIT) {
+ Log.w(TAG, "Disconnect event unhandled in state=" + mState);
+ return;
+ }
+ mNetwork = null;
+ resetStateMachine(ProvisioningCallback.OSU_FAILURE_AP_CONNECTION);
+ }
+
+ private void initiateServerConnection(Network network) {
+ if (mVerboseLoggingEnabled) {
+ Log.v(TAG, "Initiating server connection in state=" + mState);
+ }
+ if (mState != STATE_OSU_AP_CONNECTED) {
+ Log.wtf(TAG , "Initiating server connection aborted in invalid state=" + mState);
+ return;
+ }
+ if (!mOsuServerConnection.connect(mServerUrl, network)) {
+ resetStateMachine(ProvisioningCallback.OSU_FAILURE_SERVER_CONNECTION);
+ return;
+ }
+ mNetwork = network;
+ changeState(STATE_OSU_SERVER_CONNECTED);
+ invokeProvisioningCallback(PROVISIONING_STATUS,
+ ProvisioningCallback.OSU_STATUS_SERVER_CONNECTED);
+ }
+
+ private void invokeProvisioningCallback(int callbackType, int status) {
+ if (mProvisioningCallback == null) {
+ Log.e(TAG, "Provisioning callback " + callbackType + " with status " + status
+ + " not invoked");
+ return;
+ }
+ try {
+ if (callbackType == PROVISIONING_STATUS) {
+ mProvisioningCallback.onProvisioningStatus(status);
+ } else {
+ mProvisioningCallback.onProvisioningFailure(status);
+ }
+ } catch (RemoteException e) {
+ Log.e(TAG, "Remote Exception while posting callback type=" + callbackType
+ + " status=" + status);
+ }
+ }
+
+ /**
* Validate the OSU Server certificate based on the procedure in 7.3.2.2 of Hotspot2.0
* rel2 spec.
*/
@@ -407,178 +613,34 @@ public class PasspointProvisioner {
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");
+ // Sending the second sppPostDevDataRequest message.
+ if (mOsuServerConnection.exchangeSoapMessage(
+ PostDevDataMessage.serializeToSoapEnvelope(mContext, mSystemInfo,
+ mRedirectListener.getServerUrl().toString(),
+ SppConstants.SppReason.USER_INPUT_COMPLETED, mSessionId))) {
+ invokeProvisioningCallback(PROVISIONING_STATUS,
+ ProvisioningCallback.OSU_STATUS_SECOND_SOAP_EXCHANGE);
+ changeState(STATE_WAITING_FOR_SECOND_SOAP_RESPONSE);
+ } else {
+ Log.e(TAG, "HttpsConnection is not established for soap message exchange");
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();
- }
+ private PasspointConfiguration buildPasspointConfiguration(@NonNull PpsMoData moData) {
+ String moTree = moData.getPpsMoTree();
- /**
- * 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) {
- Log.v(TAG, "Connected event received in state=" + mState);
- }
- if (mState != STATE_WAITING_TO_CONNECT) {
- // Not waiting for a connection
- Log.wtf(TAG, "Connection event unhandled in state=" + mState);
- return;
- }
- invokeProvisioningCallback(PROVISIONING_STATUS,
- ProvisioningCallback.OSU_STATUS_AP_CONNECTED);
- changeState(STATE_OSU_AP_CONNECTED);
- initiateServerConnection(network);
- }
-
- private void initiateServerConnection(Network network) {
- if (mVerboseLoggingEnabled) {
- Log.v(TAG, "Initiating server connection in state=" + mState);
- }
- if (mState != STATE_OSU_AP_CONNECTED) {
- Log.wtf(TAG , "Initiating server connection aborted in invalid state=" + mState);
- return;
- }
- if (!mOsuServerConnection.connect(mServerUrl, network)) {
- resetStateMachine(ProvisioningCallback.OSU_FAILURE_SERVER_CONNECTION);
- return;
- }
- mNetwork = network;
- changeState(STATE_OSU_SERVER_CONNECTED);
- invokeProvisioningCallback(PROVISIONING_STATUS,
- ProvisioningCallback.OSU_STATUS_SERVER_CONNECTED);
- }
-
- /**
- * Disconnect event received
- *
- * Note: Called on main thread (WifiService thread).
- */
- public void handleDisconnect() {
- if (mVerboseLoggingEnabled) {
- Log.v(TAG, "Connection failed in state=" + mState);
- }
- if (mState == STATE_INIT) {
- Log.w(TAG, "Disconnect event unhandled in state=" + mState);
- return;
- }
- mNetwork = null;
- resetStateMachine(ProvisioningCallback.OSU_FAILURE_AP_CONNECTION);
- }
-
- private void invokeProvisioningCallback(int callbackType, int status) {
- if (mProvisioningCallback == null) {
- Log.e(TAG, "Provisioning callback " + callbackType + " with status " + status
- + " not invoked");
- return;
- }
- try {
- if (callbackType == PROVISIONING_STATUS) {
- mProvisioningCallback.onProvisioningStatus(status);
- } else {
- mProvisioningCallback.onProvisioningFailure(status);
+ PasspointConfiguration passpointConfiguration = PpsMoParser.parseMoText(moTree);
+ if (passpointConfiguration == null) {
+ Log.e(TAG, "fails to parse the MoTree");
+ } else {
+ if (mVerboseLoggingEnabled) {
+ Log.d(TAG, "The parsed PasspointConfiguration: " + passpointConfiguration);
}
- } catch (RemoteException e) {
- Log.e(TAG, "Remote Exception while posting callback type=" + callbackType
- + " status=" + status);
}
+ return passpointConfiguration;
}
private void changeState(int nextState) {
diff --git a/service/java/com/android/server/wifi/hotspot2/soap/command/PpsMoData.java b/service/java/com/android/server/wifi/hotspot2/soap/command/PpsMoData.java
new file mode 100644
index 000000000..20cf6de86
--- /dev/null
+++ b/service/java/com/android/server/wifi/hotspot2/soap/command/PpsMoData.java
@@ -0,0 +1,127 @@
+/*
+ * 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.command;
+
+import android.annotation.NonNull;
+import android.text.TextUtils;
+import android.util.Log;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+import org.ksoap2.serialization.PropertyInfo;
+import org.ksoap2.serialization.SoapPrimitive;
+
+import java.util.Objects;
+
+/**
+ * Represents PPS (PerProviderSubscription) MO (Management Object) defined by SPP (Subscription
+ * Provisioning Protocol).
+ */
+public class PpsMoData implements SppCommand.SppCommandData {
+ @VisibleForTesting
+ public static final String ADD_MO_COMMAND = "addMO";
+ @VisibleForTesting
+ public static final String ATTRIBUTE_MANAGEMENT_TREE_URI = "managementTreeURI";
+ @VisibleForTesting
+ public static final String ATTRIBUTE_MO_URN = "moURN";
+
+ private static final String TAG = "PpsMoData";
+ private final String mBaseUri;
+ private final String mUrn;
+ private final String mPpsMoTree;
+
+ private PpsMoData(String baseUri, String urn, String ppsMoTree) {
+ mBaseUri = baseUri;
+ mUrn = urn;
+ mPpsMoTree = ppsMoTree;
+ }
+
+ /**
+ * Create an instance of {@link PpsMoData}
+ *
+ * @param command command message embedded in SOAP sppPostDevDataResponse.
+ * @return instance of {@link PpsMoData}, {@code null} in any failure.
+ */
+ public static PpsMoData createInstance(@NonNull PropertyInfo command) {
+ if (command == null || command.getValue() == null) {
+ Log.e(TAG, "command message is null");
+ return null;
+ }
+
+ if (!TextUtils.equals(command.getName(), ADD_MO_COMMAND)) {
+ Log.e(TAG, "the response is not for addMO command");
+ return null;
+ }
+
+ if (!(command.getValue() instanceof SoapPrimitive)) {
+ Log.e(TAG, "the addMO element is not valid format");
+ return null;
+ }
+
+ SoapPrimitive soapObject = (SoapPrimitive) command.getValue();
+ if (!soapObject.hasAttribute(ATTRIBUTE_MANAGEMENT_TREE_URI)) {
+ Log.e(TAG, "managementTreeURI Attribute is missing");
+ return null;
+ }
+
+ if (!soapObject.hasAttribute(ATTRIBUTE_MO_URN)) {
+ Log.e(TAG, "moURN Attribute is missing");
+ return null;
+ }
+
+ if (soapObject.getValue() == null) {
+ Log.e(TAG, "PPSMO Tree is missing");
+ return null;
+ }
+
+ return new PpsMoData(
+ (String) soapObject.getAttributeSafelyAsString(ATTRIBUTE_MANAGEMENT_TREE_URI),
+ (String) soapObject.getAttributeSafelyAsString(ATTRIBUTE_MO_URN),
+ soapObject.getValue().toString());
+ }
+
+ /**
+ * Get PPS (PerProviderSubscription) MO (Management Object) with XML format.
+ *
+ * @return PPS MO Tree
+ */
+ public String getPpsMoTree() {
+ return mPpsMoTree;
+ }
+
+ @Override
+ public boolean equals(Object thatObject) {
+ if (this == thatObject) return true;
+ if (thatObject == null) return false;
+ if (!(thatObject instanceof PpsMoData)) return false;
+ PpsMoData ppsMoData = (PpsMoData) thatObject;
+ return TextUtils.equals(mBaseUri, ppsMoData.mBaseUri)
+ && TextUtils.equals(mUrn, ppsMoData.mUrn)
+ && TextUtils.equals(mPpsMoTree, ppsMoData.mPpsMoTree);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(mBaseUri, mUrn, mPpsMoTree);
+ }
+
+ @Override
+ public String toString() {
+ return "PpsMoData{Base URI: " + mBaseUri + ", MOURN: " + mUrn + ", PPS MO: " + mPpsMoTree
+ + "}";
+ }
+}
diff --git a/service/java/com/android/server/wifi/hotspot2/soap/command/SppCommand.java b/service/java/com/android/server/wifi/hotspot2/soap/command/SppCommand.java
index ffc8a5ab3..02df6d3ec 100644
--- a/service/java/com/android/server/wifi/hotspot2/soap/command/SppCommand.java
+++ b/service/java/com/android/server/wifi/hotspot2/soap/command/SppCommand.java
@@ -139,6 +139,7 @@ public class SppCommand {
* at the specified location to be added.
* If there is already a management object at that location, the object is replaced.
*/
+ mCommandData = PpsMoData.createInstance(soapResponse);
break;
case CommandId.UPDATE_NODE:
/*