blob: d3bb7731ca71d4dee0d48e899a26960d76e875e7 [file] [log] [blame]
/*
* Copyright 2017 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;
import android.content.Context;
import android.net.Network;
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.util.Log;
/**
* Provides methods to carry out provisioning flow
*/
public class PasspointProvisioner {
private static final String TAG = "PasspointProvisioner";
private static final int PROVISIONING_STATUS = 0;
private static final int PROVISIONING_FAILURE = 1;
private final Context mContext;
private final ProvisioningStateMachine mProvisioningStateMachine;
private final OsuNetworkCallbacks mOsuNetworkCallbacks;
private final OsuNetworkConnection mOsuNetworkConnection;
private int mCallingUid;
private boolean mVerboseLoggingEnabled = false;
PasspointProvisioner(Context context, OsuNetworkConnection osuNetworkConnection) {
mContext = context;
mOsuNetworkConnection = osuNetworkConnection;
mProvisioningStateMachine = new ProvisioningStateMachine();
mOsuNetworkCallbacks = new OsuNetworkCallbacks();
}
/**
* Sets up for provisioning
* @param looper Looper on which the Provisioning state machine will run
*/
public void init(Looper looper) {
mProvisioningStateMachine.start(new Handler(looper));
mOsuNetworkConnection.init(mProvisioningStateMachine.getHandler());
}
/**
* Enable verbose logging to help debug failures
* @param level integer indicating verbose logging enabled if > 0
*/
public void enableVerboseLogging(int level) {
mVerboseLoggingEnabled = (level > 0) ? true : false;
mOsuNetworkConnection.enableVerboseLogging(level);
}
/**
* Start provisioning flow with a given provider.
* @param callingUid calling uid.
* @param provider {@link OsuProvider} to provision with.
* @param callback {@link IProvisioningCallback} to provide provisioning status.
* @return boolean value, true if provisioning was started, false otherwise.
*
* Implements HS2.0 provisioning flow with a given HS2.0 provider.
*/
public boolean startSubscriptionProvisioning(int callingUid, OsuProvider provider,
IProvisioningCallback callback) {
mCallingUid = callingUid;
Log.v(TAG, "Provisioning started with " + provider.toString());
mProvisioningStateMachine.getHandler().post(() -> {
mProvisioningStateMachine.startProvisioning(provider, callback);
});
return true;
}
/**
* Handles the provisioning flow state transitions
*/
class ProvisioningStateMachine {
private static final String TAG = "ProvisioningStateMachine";
private static final int DEFAULT_STATE = 0;
private static final int INITIAL_STATE = 1;
private static final int WAITING_TO_CONNECT = 2;
private static final int OSU_AP_CONNECTED = 3;
private static final int FAILED_STATE = 4;
private OsuProvider mOsuProvider;
private IProvisioningCallback mProvisioningCallback;
private Handler mHandler;
private int mState;
ProvisioningStateMachine() {
mState = DEFAULT_STATE;
}
/**
* Initializes and starts the state machine with a handler to handle incoming events
*/
public void start(Handler handler) {
mHandler = handler;
changeState(INITIAL_STATE);
}
/**
* Returns the handler on which a runnable can be posted
* @return Handler State Machine's handler
*/
public Handler getHandler() {
return mHandler;
}
/**
* Start Provisioning with the Osuprovider and invoke callbacks
* @param provider OsuProvider to provision with
* @param callback IProvisioningCallback to invoke callbacks on
*/
public void startProvisioning(OsuProvider provider, IProvisioningCallback callback) {
if (mVerboseLoggingEnabled) {
Log.v(TAG, "startProvisioning received in state=" + mState);
}
if (mState != INITIAL_STATE) {
Log.v(TAG, "State Machine needs to be reset before starting provisioning");
resetStateMachine();
}
mProvisioningCallback = callback;
mOsuProvider = provider;
// Register for network and wifi state events during provisioning flow
mOsuNetworkConnection.setEventCallback(mOsuNetworkCallbacks);
if (mOsuNetworkConnection.connect(mOsuProvider.getOsuSsid(),
mOsuProvider.getNetworkAccessIdentifier())) {
invokeProvisioningCallback(PROVISIONING_STATUS,
ProvisioningCallback.OSU_STATUS_AP_CONNECTING);
changeState(WAITING_TO_CONNECT);
} else {
invokeProvisioningCallback(PROVISIONING_FAILURE,
ProvisioningCallback.OSU_FAILURE_AP_CONNECTION);
enterFailedState();
}
}
/**
* Handle Wifi Disable event
*/
public void handleWifiDisabled() {
if (mVerboseLoggingEnabled) {
Log.v(TAG, "Wifi Disabled in state=" + mState);
}
if (mState == INITIAL_STATE || mState == FAILED_STATE) {
Log.w(TAG, "Wifi Disable unhandled in state=" + mState);
return;
}
invokeProvisioningCallback(PROVISIONING_FAILURE,
ProvisioningCallback.OSU_FAILURE_AP_CONNECTION);
enterFailedState();
}
private void resetStateMachine() {
// Set to null so that no callbacks are invoked during reset
mProvisioningCallback = null;
if (mState != INITIAL_STATE || mState != FAILED_STATE) {
// Transition through Failed state to clean up
enterFailedState();
}
changeState(INITIAL_STATE);
}
/**
* Connected event received
* @param network Network object for this connection
*/
public void handleConnectedEvent(Network network) {
if (mVerboseLoggingEnabled) {
Log.v(TAG, "Connected event received in state=" + mState);
}
if (mState != WAITING_TO_CONNECT) {
// Not waiting for a connection
Log.w(TAG, "Connection event unhandled in state=" + mState);
return;
}
invokeProvisioningCallback(PROVISIONING_STATUS,
ProvisioningCallback.OSU_STATUS_AP_CONNECTED);
changeState(OSU_AP_CONNECTED);
}
/**
* Disconnect event received
*/
public void handleDisconnect() {
if (mVerboseLoggingEnabled) {
Log.v(TAG, "Connection failed in state=" + mState);
}
if (mState == INITIAL_STATE || mState == FAILED_STATE) {
Log.w(TAG, "Disconnect event unhandled in state=" + mState);
return;
}
invokeProvisioningCallback(PROVISIONING_FAILURE,
ProvisioningCallback.OSU_FAILURE_AP_CONNECTION);
enterFailedState();
}
private void changeState(int nextState) {
if (nextState != mState) {
if (mVerboseLoggingEnabled) {
Log.v(TAG, "Changing state from " + mState + " -> " + nextState);
}
mState = nextState;
}
}
private void invokeProvisioningCallback(int callbackType, int status) {
if (mProvisioningCallback == null) {
Log.e(TAG, "Provisioning callback " + callbackType + " with status " + status
+ " not invoked, callback is null");
return;
}
try {
if (callbackType == PROVISIONING_STATUS) {
mProvisioningCallback.onProvisioningStatus(status);
} else {
mProvisioningCallback.onProvisioningFailure(status);
}
} catch (RemoteException e) {
if (callbackType == PROVISIONING_STATUS) {
Log.e(TAG, "Remote Exception while posting Provisioning status " + status);
} else {
Log.e(TAG, "Remote Exception while posting Provisioning failure " + status);
}
}
}
private void enterFailedState() {
changeState(FAILED_STATE);
mOsuNetworkConnection.setEventCallback(null);
mOsuNetworkConnection.disconnectIfNeeded();
}
}
/**
* Callbacks for network and wifi events
*/
class OsuNetworkCallbacks implements OsuNetworkConnection.Callbacks {
OsuNetworkCallbacks() {
}
@Override
public void onConnected(Network network) {
Log.v(TAG, "onConnected to " + network);
if (network == null) {
mProvisioningStateMachine.getHandler().post(() -> {
mProvisioningStateMachine.handleDisconnect();
});
} else {
mProvisioningStateMachine.getHandler().post(() -> {
mProvisioningStateMachine.handleConnectedEvent(network);
});
}
}
@Override
public void onDisconnected() {
Log.v(TAG, "onDisconnected");
mProvisioningStateMachine.getHandler().post(() -> {
mProvisioningStateMachine.handleDisconnect();
});
}
@Override
public void onTimeOut() {
Log.v(TAG, "Timed out waiting for connection to OSU AP");
mProvisioningStateMachine.getHandler().post(() -> {
mProvisioningStateMachine.handleDisconnect();
});
}
@Override
public void onWifiEnabled() {
Log.v(TAG, "onWifiEnabled");
}
@Override
public void onWifiDisabled() {
Log.v(TAG, "onWifiDisabled");
mProvisioningStateMachine.getHandler().post(() -> {
mProvisioningStateMachine.handleWifiDisabled();
});
}
}
}