blob: 0245a3d0617ab7925fae00fd751753517a886be1 [file] [log] [blame]
/*
* Copyright (C) 2014 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 android.net.wifi.passpoint;
import android.content.Context;
import android.net.wifi.ScanResult;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
import android.os.Messenger;
import android.os.Parcel;
import android.os.Parcelable;
import android.os.RemoteException;
import android.util.Log;
import com.android.internal.util.AsyncChannel;
import com.android.internal.util.Protocol;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
/**
* Provides APIs for managing Wifi Passpoint credentials.
* @hide
*/
public class WifiPasspointManager {
private static final String TAG = "PasspointManager";
private static final boolean DBG = true;
/* Passpoint states values */
/** Passpoint is in an unknown state. This should only occur in boot time */
public static final int PASSPOINT_STATE_UNKNOWN = 0;
/** Passpoint is disabled. This occurs when wifi is disabled */
public static final int PASSPOINT_STATE_DISABLED = 1;
/** Passpoint is enabled and in discovery state */
public static final int PASSPOINT_STATE_DISCOVERY = 2;
/** Passpoint is enabled and in access state */
public static final int PASSPOINT_STATE_ACCESS = 3;
/** Passpoint is enabled and in provisioning state */
public static final int PASSPOINT_STATE_PROVISION = 4;
/* Passpoint callback error codes */
/** Indicates that the operation failed due to an internal error */
public static final int REASON_ERROR = 0;
/** Indicates that the operation failed because wifi is disabled */
public static final int REASON_WIFI_DISABLED = 1;
/** Indicates that the operation failed because the framework is busy */
public static final int REASON_BUSY = 2;
/** Indicates that the operation failed because parameter is invalid */
public static final int REASON_INVALID_PARAMETER = 3;
/** Indicates that the operation failed because the server is not trusted */
public static final int REASON_NOT_TRUSTED = 4;
/**
* protocol supported for Passpoint
*/
public static final String PROTOCOL_DM = "OMA-DM-ClientInitiated";
/**
* protocol supported for Passpoint
*/
public static final String PROTOCOL_SOAP = "SPP-ClientInitiated";
/* Passpoint broadcasts */
/**
* Broadcast intent action indicating that the state of Passpoint
* connectivity has changed
*/
public static final String PASSPOINT_STATE_CHANGED_ACTION =
"android.net.wifi.passpoint.STATE_CHANGE";
/**
* Broadcast intent action indicating that the saved Passpoint credential
* list has changed
*/
public static final String PASSPOINT_CRED_CHANGED_ACTION =
"android.net.wifi.passpoint.CRED_CHANGE";
/**
* Broadcast intent action indicating that Passpoint online sign up is
* avaiable.
*/
public static final String PASSPOINT_OSU_AVAILABLE_ACTION =
"android.net.wifi.passpoint.OSU_AVAILABLE";
/**
* Broadcast intent action indicating that user remediation is required
*/
public static final String PASSPOINT_USER_REM_REQ_ACTION =
"android.net.wifi.passpoint.USER_REM_REQ";
/**
* Interface for callback invocation when framework channel is lost
*/
public interface ChannelListener {
/**
* The channel to the framework has been disconnected. Application could
* try re-initializing using {@link #initialize}
*/
public void onChannelDisconnected();
}
/**
* Interface for callback invocation on an application action
*/
public interface ActionListener {
/** The operation succeeded */
public void onSuccess();
/**
* The operation failed
*
* @param reason The reason for failure could be one of
* {@link #WIFI_DISABLED}, {@link #ERROR} or {@link #BUSY}
*/
public void onFailure(int reason);
}
/**
* Interface for callback invocation when doing OSU or user remediation
*/
public interface OsuRemListener {
/** The operation succeeded */
public void onSuccess();
/**
* The operation failed
*
* @param reason The reason for failure could be one of
* {@link #WIFI_DISABLED}, {@link #ERROR} or {@link #BUSY}
*/
public void onFailure(int reason);
/**
* Browser launch is requried for user interaction. When this callback
* is called, app should launch browser / webview to the given URI.
*
* @param uri URI for browser launch
*/
public void onBrowserLaunch(String uri);
/**
* When this is called, app should dismiss the previously lanched browser.
*/
public void onBrowserDismiss();
}
/**
* A channel that connects the application to the wifi passpoint framework.
* Most passpoint operations require a Channel as an argument.
* An instance of Channel is obtained by doing a call on {@link #initialize}
*/
public static class Channel {
private final static int INVALID_LISTENER_KEY = 0;
private ChannelListener mChannelListener;
private HashMap<Integer, Object> mListenerMap = new HashMap<Integer, Object>();
private HashMap<Integer, Integer> mListenerMapCount = new HashMap<Integer, Integer>();
private Object mListenerMapLock = new Object();
private int mListenerKey = 0;
private List<ScanResult> mAnqpRequest = new LinkedList<ScanResult>();
private Object mAnqpRequestLock = new Object();
private AsyncChannel mAsyncChannel;
private PasspointHandler mHandler;
Context mContext;
Channel(Context context, Looper looper, ChannelListener l) {
mAsyncChannel = new AsyncChannel();
mHandler = new PasspointHandler(looper);
mChannelListener = l;
mContext = context;
}
private int putListener(Object listener) {
return putListener(listener, 1);
}
private int putListener(Object listener, int count) {
if (listener == null || count <= 0)
return INVALID_LISTENER_KEY;
int key;
synchronized (mListenerMapLock) {
do {
key = mListenerKey++;
} while (key == INVALID_LISTENER_KEY);
mListenerMap.put(key, listener);
mListenerMapCount.put(key, count);
}
return key;
}
private Object peekListener(int key) {
Log.d(TAG, "peekListener() key=" + key);
if (key == INVALID_LISTENER_KEY)
return null;
synchronized (mListenerMapLock) {
return mListenerMap.get(key);
}
}
private Object getListener(int key, boolean forceRemove) {
Log.d(TAG, "getListener() key=" + key + " force=" + forceRemove);
if (key == INVALID_LISTENER_KEY)
return null;
synchronized (mListenerMapLock) {
if (!forceRemove) {
int count = mListenerMapCount.get(key);
Log.d(TAG, "count=" + count);
mListenerMapCount.put(key, --count);
if (count > 0)
return null;
}
Log.d(TAG, "remove key");
mListenerMapCount.remove(key);
return mListenerMap.remove(key);
}
}
private void anqpRequestStart(ScanResult sr) {
Log.d(TAG, "anqpRequestStart sr.bssid=" + sr.BSSID);
synchronized (mAnqpRequestLock) {
mAnqpRequest.add(sr);
}
}
private void anqpRequestFinish(WifiPasspointInfo result) {
Log.d(TAG, "anqpRequestFinish pi.bssid=" + result.bssid);
synchronized (mAnqpRequestLock) {
for (ScanResult sr : mAnqpRequest)
if (sr.BSSID.equals(result.bssid)) {
Log.d(TAG, "find hit " + result.bssid);
/* sr.passpoint = result; */
mAnqpRequest.remove(sr);
Log.d(TAG, "mAnqpRequest.len=" + mAnqpRequest.size());
break;
}
}
}
private void anqpRequestFinish(ScanResult sr) {
Log.d(TAG, "anqpRequestFinish sr.bssid=" + sr.BSSID);
synchronized (mAnqpRequestLock) {
for (ScanResult sr1 : mAnqpRequest)
if (sr1.BSSID.equals(sr.BSSID)) {
mAnqpRequest.remove(sr1);
break;
}
}
}
class PasspointHandler extends Handler {
PasspointHandler(Looper looper) {
super(looper);
}
@Override
public void handleMessage(Message message) {
Object listener = null;
switch (message.what) {
case AsyncChannel.CMD_CHANNEL_DISCONNECTED:
if (mChannelListener != null) {
mChannelListener.onChannelDisconnected();
mChannelListener = null;
}
break;
case REQUEST_ANQP_INFO_SUCCEEDED:
WifiPasspointInfo result = (WifiPasspointInfo) message.obj;
anqpRequestFinish(result);
listener = getListener(message.arg2, false);
if (listener != null) {
((ActionListener) listener).onSuccess();
}
break;
case REQUEST_ANQP_INFO_FAILED:
anqpRequestFinish((ScanResult) message.obj);
listener = getListener(message.arg2, false);
if (listener == null)
getListener(message.arg2, true);
if (listener != null) {
((ActionListener) listener).onFailure(message.arg1);
}
break;
case START_OSU_SUCCEEDED:
listener = getListener(message.arg2, true);
if (listener != null) {
((OsuRemListener) listener).onSuccess();
}
break;
case START_OSU_FAILED:
listener = getListener(message.arg2, true);
if (listener != null) {
((OsuRemListener) listener).onFailure(message.arg1);
}
break;
case START_OSU_BROWSER:
listener = peekListener(message.arg2);
if (listener != null) {
ParcelableString str = (ParcelableString) message.obj;
if (str == null || str.string == null)
((OsuRemListener) listener).onBrowserDismiss();
else
((OsuRemListener) listener).onBrowserLaunch(str.string);
}
break;
default:
Log.d(TAG, "Ignored " + message);
break;
}
}
}
}
public static class ParcelableString implements Parcelable {
public String string;
@Override
public int describeContents() {
return 0;
}
@Override
public void writeToParcel(Parcel out, int flags) {
out.writeString(string);
}
public static final Parcelable.Creator<ParcelableString> CREATOR =
new Parcelable.Creator<ParcelableString>() {
@Override
public ParcelableString createFromParcel(Parcel in) {
ParcelableString ret = new ParcelableString();
ret.string = in.readString();
return ret;
}
@Override
public ParcelableString[] newArray(int size) {
return new ParcelableString[size];
}
};
}
private static final int BASE = Protocol.BASE_WIFI_PASSPOINT_MANAGER;
public static final int REQUEST_ANQP_INFO = BASE + 1;
public static final int REQUEST_ANQP_INFO_FAILED = BASE + 2;
public static final int REQUEST_ANQP_INFO_SUCCEEDED = BASE + 3;
public static final int REQUEST_OSU_ICON = BASE + 4;
public static final int REQUEST_OSU_ICON_FAILED = BASE + 5;
public static final int REQUEST_OSU_ICON_SUCCEEDED = BASE + 6;
public static final int START_OSU = BASE + 7;
public static final int START_OSU_BROWSER = BASE + 8;
public static final int START_OSU_FAILED = BASE + 9;
public static final int START_OSU_SUCCEEDED = BASE + 10;
private Context mContext;
IWifiPasspointManager mService;
/**
* TODO: doc
* @param context
* @param service
*/
public WifiPasspointManager(Context context, IWifiPasspointManager service) {
mContext = context;
mService = service;
}
/**
* Registers the application with the framework. This function must be the
* first to be called before any async passpoint operations are performed.
*
* @param srcContext is the context of the source
* @param srcLooper is the Looper on which the callbacks are receivied
* @param listener for callback at loss of framework communication. Can be
* null.
* @return Channel instance that is necessary for performing any further
* passpoint operations
*
*/
public Channel initialize(Context srcContext, Looper srcLooper, ChannelListener listener) {
Messenger messenger = getMessenger();
if (messenger == null)
return null;
Channel c = new Channel(srcContext, srcLooper, listener);
if (c.mAsyncChannel.connectSync(srcContext, c.mHandler, messenger)
== AsyncChannel.STATUS_SUCCESSFUL) {
return c;
} else {
return null;
}
}
/**
* STOPSHIP: temp solution, should use supplicant manager instead, check
* with b/13931972
*/
public Messenger getMessenger() {
try {
return mService.getMessenger();
} catch (RemoteException e) {
return null;
}
}
public int getPasspointState() {
try {
return mService.getPasspointState();
} catch (RemoteException e) {
return PASSPOINT_STATE_UNKNOWN;
}
}
public void requestAnqpInfo(Channel c, List<ScanResult> requested, int mask,
ActionListener listener) {
Log.d(TAG, "requestAnqpInfo start");
Log.d(TAG, "requested.size=" + requested.size());
checkChannel(c);
List<ScanResult> list = new ArrayList<ScanResult>();
for (ScanResult sr : requested)
if (sr.capabilities.contains("[HS20]")) {
list.add(sr);
c.anqpRequestStart(sr);
Log.d(TAG, "adding " + sr.BSSID);
}
int count = list.size();
Log.d(TAG, "after filter, count=" + count);
if (count == 0) {
if (DBG)
Log.d(TAG, "ANQP info request contains no HS20 APs, skipped");
listener.onSuccess();
return;
}
int key = c.putListener(listener, count);
for (ScanResult sr : list)
c.mAsyncChannel.sendMessage(REQUEST_ANQP_INFO, mask, key, sr);
Log.d(TAG, "requestAnqpInfo end");
}
public void requestOsuIcons(Channel c, List<WifiPasspointOsuProvider> requested,
int resolution, ActionListener listener) {
}
public List<WifiPasspointPolicy> requestCredentialMatch(List<ScanResult> requested) {
try {
return mService.requestCredentialMatch(requested);
} catch (RemoteException e) {
return null;
}
}
/**
* Get a list of saved Passpoint credentials. Only those credentials owned
* by the caller will be returned.
*
* @return The list of credentials
*/
public List<WifiPasspointCredential> getCredentials() {
try {
return mService.getCredentials();
} catch (RemoteException e) {
return null;
}
}
/**
* Add a new Passpoint credential.
*
* @param cred The credential to be added
* @return {@code true} if the operation succeeds, {@code false} otherwise
*/
public boolean addCredential(WifiPasspointCredential cred) {
try {
return mService.addCredential(cred);
} catch (RemoteException e) {
return false;
}
}
/**
* Update an existing Passpoint credential. Only system or the owner of this
* credential has the permission to do this.
*
* @param cred The credential to be updated
* @return {@code true} if the operation succeeds, {@code false} otherwise
*/
public boolean updateCredential(WifiPasspointCredential cred) {
try {
return mService.updateCredential(cred);
} catch (RemoteException e) {
return false;
}
}
/**
* Remove an existing Passpoint credential. Only system or the owner of this
* credential has the permission to do this.
*
* @param cred The credential to be removed
* @return {@code true} if the operation succeeds, {@code false} otherwise
*/
public boolean removeCredential(WifiPasspointCredential cred) {
try {
return mService.removeCredential(cred);
} catch (RemoteException e) {
return false;
}
}
public void startOsu(Channel c, WifiPasspointOsuProvider osu, OsuRemListener listener) {
Log.d(TAG, "startOsu start");
checkChannel(c);
int key = c.putListener(listener);
c.mAsyncChannel.sendMessage(START_OSU, 0, key, osu);
Log.d(TAG, "startOsu end");
}
public void startRemediation(Channel c, OsuRemListener listener) {
}
public void connect(WifiPasspointPolicy policy) {
}
private static void checkChannel(Channel c) {
if (c == null) throw new IllegalArgumentException("Channel needs to be initialized");
}
}