/*
|
* Copyright (C) 2016 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.p2p;
|
|
import android.annotation.NonNull;
|
import android.hardware.wifi.supplicant.V1_0.ISupplicant;
|
import android.hardware.wifi.supplicant.V1_0.ISupplicantIface;
|
import android.hardware.wifi.supplicant.V1_0.ISupplicantNetwork;
|
import android.hardware.wifi.supplicant.V1_0.ISupplicantP2pIface;
|
import android.hardware.wifi.supplicant.V1_0.ISupplicantP2pIfaceCallback;
|
import android.hardware.wifi.supplicant.V1_0.ISupplicantP2pNetwork;
|
import android.hardware.wifi.supplicant.V1_0.IfaceType;
|
import android.hardware.wifi.supplicant.V1_0.SupplicantStatus;
|
import android.hardware.wifi.supplicant.V1_0.SupplicantStatusCode;
|
import android.hardware.wifi.supplicant.V1_0.WpsConfigMethods;
|
import android.hidl.manager.V1_0.IServiceManager;
|
import android.hidl.manager.V1_0.IServiceNotification;
|
import android.net.wifi.WpsInfo;
|
import android.net.wifi.p2p.WifiP2pConfig;
|
import android.net.wifi.p2p.WifiP2pDevice;
|
import android.net.wifi.p2p.WifiP2pGroup;
|
import android.net.wifi.p2p.WifiP2pGroupList;
|
import android.net.wifi.p2p.WifiP2pManager;
|
import android.net.wifi.p2p.nsd.WifiP2pServiceInfo;
|
import android.os.HwRemoteBinder;
|
import android.os.RemoteException;
|
import android.text.TextUtils;
|
import android.util.Log;
|
|
import com.android.internal.util.ArrayUtils;
|
import com.android.server.wifi.util.NativeUtil;
|
|
import java.nio.ByteBuffer;
|
import java.nio.ByteOrder;
|
import java.util.ArrayList;
|
import java.util.Arrays;
|
import java.util.List;
|
import java.util.NoSuchElementException;
|
import java.util.regex.Matcher;
|
import java.util.regex.Pattern;
|
import java.util.stream.Collectors;
|
|
/**
|
* Native calls sending requests to the P2P Hals, and callbacks for receiving P2P events
|
*
|
* {@hide}
|
*/
|
public class SupplicantP2pIfaceHal {
|
private static final String TAG = "SupplicantP2pIfaceHal";
|
private static boolean sVerboseLoggingEnabled = true;
|
private static final int RESULT_NOT_VALID = -1;
|
private static final int DEFAULT_GROUP_OWNER_INTENT = 6;
|
private static final int DEFAULT_OPERATING_CLASS = 81;
|
/**
|
* Regex pattern for extracting the wps device type bytes.
|
* Matches a strings like the following: "<categ>-<OUI>-<subcateg>";
|
*/
|
private static final Pattern WPS_DEVICE_TYPE_PATTERN =
|
Pattern.compile("^(\\d{1,2})-([0-9a-fA-F]{8})-(\\d{1,2})$");
|
|
private Object mLock = new Object();
|
|
// Supplicant HAL HIDL interface objects
|
private IServiceManager mIServiceManager = null;
|
private ISupplicant mISupplicant = null;
|
private ISupplicantIface mHidlSupplicantIface = null;
|
private ISupplicantP2pIface mISupplicantP2pIface = null;
|
private final IServiceNotification mServiceNotificationCallback =
|
new IServiceNotification.Stub() {
|
public void onRegistration(String fqName, String name, boolean preexisting) {
|
synchronized (mLock) {
|
if (sVerboseLoggingEnabled) {
|
Log.i(TAG, "IServiceNotification.onRegistration for: " + fqName
|
+ ", " + name + " preexisting=" + preexisting);
|
}
|
if (!initSupplicantService()) {
|
Log.e(TAG, "initalizing ISupplicant failed.");
|
supplicantServiceDiedHandler();
|
} else {
|
Log.i(TAG, "Completed initialization of ISupplicant interfaces.");
|
}
|
}
|
}
|
};
|
private final HwRemoteBinder.DeathRecipient mServiceManagerDeathRecipient =
|
cookie -> {
|
Log.w(TAG, "IServiceManager died: cookie=" + cookie);
|
synchronized (mLock) {
|
supplicantServiceDiedHandler();
|
mIServiceManager = null; // Will need to register a new ServiceNotification
|
}
|
};
|
private final HwRemoteBinder.DeathRecipient mSupplicantDeathRecipient =
|
cookie -> {
|
Log.w(TAG, "ISupplicant/ISupplicantStaIface died: cookie=" + cookie);
|
synchronized (mLock) {
|
supplicantServiceDiedHandler();
|
}
|
};
|
|
private final WifiP2pMonitor mMonitor;
|
private SupplicantP2pIfaceCallback mCallback = null;
|
|
public SupplicantP2pIfaceHal(WifiP2pMonitor monitor) {
|
mMonitor = monitor;
|
}
|
|
private boolean linkToServiceManagerDeath() {
|
if (mIServiceManager == null) return false;
|
try {
|
if (!mIServiceManager.linkToDeath(mServiceManagerDeathRecipient, 0)) {
|
Log.wtf(TAG, "Error on linkToDeath on IServiceManager");
|
supplicantServiceDiedHandler();
|
mIServiceManager = null; // Will need to register a new ServiceNotification
|
return false;
|
}
|
} catch (RemoteException e) {
|
Log.e(TAG, "IServiceManager.linkToDeath exception", e);
|
return false;
|
}
|
return true;
|
}
|
|
/**
|
* Enable verbose logging for all sub modules.
|
*/
|
public static void enableVerboseLogging(int verbose) {
|
sVerboseLoggingEnabled = verbose > 0;
|
SupplicantP2pIfaceCallback.enableVerboseLogging(verbose);
|
}
|
|
/**
|
* Registers a service notification for the ISupplicant service, which triggers intialization of
|
* the ISupplicantP2pIface
|
* @return true if the service notification was successfully registered
|
*/
|
public boolean initialize() {
|
if (sVerboseLoggingEnabled) Log.i(TAG, "Registering ISupplicant service ready callback.");
|
synchronized (mLock) {
|
if (mIServiceManager != null) {
|
Log.i(TAG, "Supplicant HAL already initialized.");
|
// Already have an IServiceManager and serviceNotification registered, don't
|
// don't register another.
|
return true;
|
}
|
mISupplicant = null;
|
mISupplicantP2pIface = null;
|
try {
|
mIServiceManager = getServiceManagerMockable();
|
if (mIServiceManager == null) {
|
Log.e(TAG, "Failed to get HIDL Service Manager");
|
return false;
|
}
|
if (!linkToServiceManagerDeath()) {
|
return false;
|
}
|
/* TODO(b/33639391) : Use the new ISupplicant.registerForNotifications() once it
|
exists */
|
if (!mIServiceManager.registerForNotifications(
|
ISupplicant.kInterfaceName, "", mServiceNotificationCallback)) {
|
Log.e(TAG, "Failed to register for notifications to "
|
+ ISupplicant.kInterfaceName);
|
mIServiceManager = null; // Will need to register a new ServiceNotification
|
return false;
|
}
|
|
// Successful completion by the end of the 'try' block. This will prevent reporting
|
// proper initialization after exception is caught.
|
return true;
|
} catch (RemoteException e) {
|
Log.e(TAG, "Exception while trying to register a listener for ISupplicant service: "
|
+ e);
|
supplicantServiceDiedHandler();
|
}
|
return false;
|
}
|
}
|
|
private boolean linkToSupplicantDeath() {
|
if (mISupplicant == null) return false;
|
try {
|
if (!mISupplicant.linkToDeath(mSupplicantDeathRecipient, 0)) {
|
Log.wtf(TAG, "Error on linkToDeath on ISupplicant");
|
supplicantServiceDiedHandler();
|
return false;
|
}
|
} catch (RemoteException e) {
|
Log.e(TAG, "ISupplicant.linkToDeath exception", e);
|
return false;
|
}
|
return true;
|
}
|
|
private boolean initSupplicantService() {
|
synchronized (mLock) {
|
try {
|
mISupplicant = getSupplicantMockable();
|
} catch (RemoteException e) {
|
Log.e(TAG, "ISupplicant.getService exception: " + e);
|
return false;
|
}
|
if (mISupplicant == null) {
|
Log.e(TAG, "Got null ISupplicant service. Stopping supplicant HIDL startup");
|
return false;
|
}
|
if (!linkToSupplicantDeath()) {
|
return false;
|
}
|
}
|
return true;
|
}
|
|
private boolean linkToSupplicantP2pIfaceDeath() {
|
if (mISupplicantP2pIface == null) return false;
|
try {
|
if (!mISupplicantP2pIface.linkToDeath(mSupplicantDeathRecipient, 0)) {
|
Log.wtf(TAG, "Error on linkToDeath on ISupplicantP2pIface");
|
supplicantServiceDiedHandler();
|
return false;
|
}
|
} catch (RemoteException e) {
|
Log.e(TAG, "ISupplicantP2pIface.linkToDeath exception", e);
|
return false;
|
}
|
return true;
|
}
|
|
/**
|
* Setup the P2p iface.
|
*
|
* @param ifaceName Name of the interface.
|
* @return true on success, false otherwise.
|
*/
|
public boolean setupIface(@NonNull String ifaceName) {
|
synchronized (mLock) {
|
if (mISupplicantP2pIface != null) return false;
|
ISupplicantIface ifaceHwBinder;
|
if (isV1_1()) {
|
ifaceHwBinder = addIfaceV1_1(ifaceName);
|
} else {
|
ifaceHwBinder = getIfaceV1_0(ifaceName);
|
}
|
if (ifaceHwBinder == null) {
|
Log.e(TAG, "initSupplicantP2pIface got null iface");
|
return false;
|
}
|
mISupplicantP2pIface = getP2pIfaceMockable(ifaceHwBinder);
|
if (!linkToSupplicantP2pIfaceDeath()) {
|
return false;
|
}
|
if (mISupplicantP2pIface != null && mMonitor != null) {
|
mCallback = new SupplicantP2pIfaceCallback(ifaceName, mMonitor);
|
if (!registerCallback(mCallback)) {
|
Log.e(TAG, "Callback registration failed. Initialization incomplete.");
|
return false;
|
}
|
}
|
return true;
|
}
|
}
|
|
private ISupplicantIface getIfaceV1_0(@NonNull String ifaceName) {
|
/** List all supplicant Ifaces */
|
final ArrayList<ISupplicant.IfaceInfo> supplicantIfaces = new ArrayList();
|
try {
|
mISupplicant.listInterfaces((SupplicantStatus status,
|
ArrayList<ISupplicant.IfaceInfo> ifaces) -> {
|
if (status.code != SupplicantStatusCode.SUCCESS) {
|
Log.e(TAG, "Getting Supplicant Interfaces failed: " + status.code);
|
return;
|
}
|
supplicantIfaces.addAll(ifaces);
|
});
|
} catch (RemoteException e) {
|
Log.e(TAG, "ISupplicant.listInterfaces exception: " + e);
|
return null;
|
}
|
if (supplicantIfaces.size() == 0) {
|
Log.e(TAG, "Got zero HIDL supplicant ifaces. Stopping supplicant HIDL startup.");
|
supplicantServiceDiedHandler();
|
return null;
|
}
|
SupplicantResult<ISupplicantIface> supplicantIface =
|
new SupplicantResult("getInterface()");
|
for (ISupplicant.IfaceInfo ifaceInfo : supplicantIfaces) {
|
if (ifaceInfo.type == IfaceType.P2P && ifaceName.equals(ifaceInfo.name)) {
|
try {
|
mISupplicant.getInterface(ifaceInfo,
|
(SupplicantStatus status, ISupplicantIface iface) -> {
|
if (status.code != SupplicantStatusCode.SUCCESS) {
|
Log.e(TAG, "Failed to get ISupplicantIface " + status.code);
|
return;
|
}
|
supplicantIface.setResult(status, iface);
|
});
|
} catch (RemoteException e) {
|
Log.e(TAG, "ISupplicant.getInterface exception: " + e);
|
supplicantServiceDiedHandler();
|
return null;
|
}
|
break;
|
}
|
}
|
return supplicantIface.getResult();
|
}
|
|
private ISupplicantIface addIfaceV1_1(@NonNull String ifaceName) {
|
synchronized (mLock) {
|
ISupplicant.IfaceInfo ifaceInfo = new ISupplicant.IfaceInfo();
|
ifaceInfo.name = ifaceName;
|
ifaceInfo.type = IfaceType.P2P;
|
SupplicantResult<ISupplicantIface> supplicantIface =
|
new SupplicantResult("addInterface(" + ifaceInfo + ")");
|
try {
|
android.hardware.wifi.supplicant.V1_1.ISupplicant supplicant_v1_1 =
|
getSupplicantMockableV1_1();
|
if (supplicant_v1_1 == null) {
|
Log.e(TAG, "Can't call addIface: ISupplicantP2pIface is null");
|
return null;
|
}
|
supplicant_v1_1.addInterface(ifaceInfo,
|
(SupplicantStatus status, ISupplicantIface iface) -> {
|
if (status.code != SupplicantStatusCode.SUCCESS
|
&& status.code != SupplicantStatusCode.FAILURE_IFACE_EXISTS) {
|
Log.e(TAG, "Failed to get ISupplicantIface " + status.code);
|
return;
|
}
|
supplicantIface.setResult(status, iface);
|
});
|
} catch (RemoteException e) {
|
Log.e(TAG, "ISupplicant.addInterface exception: " + e);
|
supplicantServiceDiedHandler();
|
return null;
|
}
|
return supplicantIface.getResult();
|
}
|
}
|
|
/**
|
* Teardown the P2P interface.
|
*
|
* @param ifaceName Name of the interface.
|
* @return true on success, false otherwise.
|
*/
|
public boolean teardownIface(@NonNull String ifaceName) {
|
synchronized (mLock) {
|
if (mISupplicantP2pIface == null) return false;
|
// Only supported for V1.1
|
if (isV1_1()) {
|
return removeIfaceV1_1(ifaceName);
|
}
|
return true;
|
}
|
}
|
|
/**
|
* Remove the P2p iface.
|
*
|
* @return true on success, false otherwise.
|
*/
|
private boolean removeIfaceV1_1(@NonNull String ifaceName) {
|
synchronized (mLock) {
|
try {
|
android.hardware.wifi.supplicant.V1_1.ISupplicant supplicant_v1_1 =
|
getSupplicantMockableV1_1();
|
if (supplicant_v1_1 == null) {
|
Log.e(TAG, "Can't call removeIface: ISupplicantP2pIface is null");
|
return false;
|
}
|
ISupplicant.IfaceInfo ifaceInfo = new ISupplicant.IfaceInfo();
|
ifaceInfo.name = ifaceName;
|
ifaceInfo.type = IfaceType.P2P;
|
SupplicantStatus status = supplicant_v1_1.removeInterface(ifaceInfo);
|
if (status.code != SupplicantStatusCode.SUCCESS) {
|
Log.e(TAG, "Failed to remove iface " + status.code);
|
return false;
|
}
|
mCallback = null;
|
} catch (RemoteException e) {
|
Log.e(TAG, "ISupplicant.removeInterface exception: " + e);
|
supplicantServiceDiedHandler();
|
return false;
|
}
|
mISupplicantP2pIface = null;
|
return true;
|
}
|
}
|
|
private void supplicantServiceDiedHandler() {
|
synchronized (mLock) {
|
mISupplicant = null;
|
mISupplicantP2pIface = null;
|
}
|
}
|
|
|
/**
|
* Signals whether Initialization completed successfully.
|
*/
|
public boolean isInitializationStarted() {
|
synchronized (mLock) {
|
return mIServiceManager != null;
|
}
|
}
|
|
/**
|
* Signals whether Initialization completed successfully. Only necessary for testing, is not
|
* needed to guard calls etc.
|
*/
|
public boolean isInitializationComplete() {
|
return mISupplicant != null;
|
}
|
|
/**
|
* Wrapper functions to access static HAL methods, created to be mockable in unit tests
|
*/
|
protected IServiceManager getServiceManagerMockable() throws RemoteException {
|
return IServiceManager.getService();
|
}
|
|
protected ISupplicant getSupplicantMockable() throws RemoteException {
|
try {
|
return ISupplicant.getService();
|
} catch (NoSuchElementException e) {
|
Log.e(TAG, "Failed to get ISupplicant", e);
|
return null;
|
}
|
}
|
|
protected android.hardware.wifi.supplicant.V1_1.ISupplicant getSupplicantMockableV1_1()
|
throws RemoteException {
|
synchronized (mLock) {
|
try {
|
return android.hardware.wifi.supplicant.V1_1.ISupplicant.castFrom(
|
ISupplicant.getService());
|
} catch (NoSuchElementException e) {
|
Log.e(TAG, "Failed to get ISupplicant", e);
|
return null;
|
}
|
}
|
}
|
|
protected ISupplicantP2pIface getP2pIfaceMockable(ISupplicantIface iface) {
|
return ISupplicantP2pIface.asInterface(iface.asBinder());
|
}
|
|
protected android.hardware.wifi.supplicant.V1_2.ISupplicantP2pIface
|
getP2pIfaceMockableV1_2() {
|
if (mISupplicantP2pIface == null) return null;
|
return android.hardware.wifi.supplicant.V1_2.ISupplicantP2pIface.castFrom(
|
mISupplicantP2pIface);
|
}
|
|
protected ISupplicantP2pNetwork getP2pNetworkMockable(ISupplicantNetwork network) {
|
return ISupplicantP2pNetwork.asInterface(network.asBinder());
|
}
|
|
/**
|
* Check if the device is running V1_1 supplicant service.
|
* @return
|
*/
|
private boolean isV1_1() {
|
synchronized (mLock) {
|
try {
|
return (getSupplicantMockableV1_1() != null);
|
} catch (RemoteException e) {
|
Log.e(TAG, "ISupplicant.getService exception: " + e);
|
supplicantServiceDiedHandler();
|
return false;
|
}
|
}
|
}
|
|
protected static void logd(String s) {
|
if (sVerboseLoggingEnabled) Log.d(TAG, s);
|
}
|
|
protected static void logCompletion(String operation, SupplicantStatus status) {
|
if (status == null) {
|
Log.w(TAG, operation + " failed: no status code returned.");
|
} else if (status.code == SupplicantStatusCode.SUCCESS) {
|
logd(operation + " completed successfully.");
|
} else {
|
Log.w(TAG, operation + " failed: " + status.code + " (" + status.debugMessage + ")");
|
}
|
}
|
|
|
/**
|
* Returns false if SupplicantP2pIface is null, and logs failure to call methodStr
|
*/
|
private boolean checkSupplicantP2pIfaceAndLogFailure(String method) {
|
if (mISupplicantP2pIface == null) {
|
Log.e(TAG, "Can't call " + method + ": ISupplicantP2pIface is null");
|
return false;
|
}
|
return true;
|
}
|
|
/**
|
* Returns false if SupplicantP2pIface is null, and logs failure to call methodStr
|
*/
|
private boolean checkSupplicantP2pIfaceAndLogFailureV1_2(String method) {
|
if (getP2pIfaceMockableV1_2() == null) {
|
Log.e(TAG, "Can't call " + method + ": ISupplicantP2pIface is null");
|
return false;
|
}
|
return true;
|
}
|
|
private int wpsInfoToConfigMethod(int info) {
|
switch (info) {
|
case WpsInfo.PBC:
|
return ISupplicantP2pIface.WpsProvisionMethod.PBC;
|
|
case WpsInfo.DISPLAY:
|
return ISupplicantP2pIface.WpsProvisionMethod.DISPLAY;
|
|
case WpsInfo.KEYPAD:
|
case WpsInfo.LABEL:
|
return ISupplicantP2pIface.WpsProvisionMethod.KEYPAD;
|
|
default:
|
Log.e(TAG, "Unsupported WPS provision method: " + info);
|
return RESULT_NOT_VALID;
|
}
|
}
|
|
/**
|
* Retrieves the name of the network interface.
|
*
|
* @return name Name of the network interface, e.g., wlan0
|
*/
|
public String getName() {
|
synchronized (mLock) {
|
if (!checkSupplicantP2pIfaceAndLogFailure("getName")) return null;
|
SupplicantResult<String> result = new SupplicantResult("getName()");
|
|
try {
|
mISupplicantP2pIface.getName(
|
(SupplicantStatus status, String name) -> {
|
result.setResult(status, name);
|
});
|
} catch (RemoteException e) {
|
Log.e(TAG, "ISupplicantP2pIface exception: " + e);
|
supplicantServiceDiedHandler();
|
}
|
return result.getResult();
|
}
|
}
|
|
|
/**
|
* Register for callbacks from this interface.
|
*
|
* These callbacks are invoked for events that are specific to this interface.
|
* Registration of multiple callback objects is supported. These objects must
|
* be automatically deleted when the corresponding client process is dead or
|
* if this interface is removed.
|
*
|
* @param receiver An instance of the |ISupplicantP2pIfaceCallback| HIDL
|
* interface object.
|
* @return boolean value indicating whether operation was successful.
|
*/
|
public boolean registerCallback(ISupplicantP2pIfaceCallback receiver) {
|
synchronized (mLock) {
|
if (!checkSupplicantP2pIfaceAndLogFailure("registerCallback")) return false;
|
SupplicantResult<Void> result = new SupplicantResult("registerCallback()");
|
try {
|
result.setResult(mISupplicantP2pIface.registerCallback(receiver));
|
} catch (RemoteException e) {
|
Log.e(TAG, "ISupplicantP2pIface exception: " + e);
|
supplicantServiceDiedHandler();
|
}
|
return result.isSuccess();
|
}
|
}
|
|
|
/**
|
* Initiate a P2P service discovery with a (optional) timeout.
|
*
|
* @param timeout Max time to be spent is peforming discovery.
|
* Set to 0 to indefinely continue discovery untill and explicit
|
* |stopFind| is sent.
|
* @return boolean value indicating whether operation was successful.
|
*/
|
public boolean find(int timeout) {
|
synchronized (mLock) {
|
if (!checkSupplicantP2pIfaceAndLogFailure("find")) return false;
|
|
if (timeout < 0) {
|
Log.e(TAG, "Invalid timeout value: " + timeout);
|
return false;
|
}
|
SupplicantResult<Void> result = new SupplicantResult("find(" + timeout + ")");
|
try {
|
result.setResult(mISupplicantP2pIface.find(timeout));
|
} catch (RemoteException e) {
|
Log.e(TAG, "ISupplicantP2pIface exception: " + e);
|
supplicantServiceDiedHandler();
|
}
|
return result.isSuccess();
|
}
|
}
|
|
|
/**
|
* Stop an ongoing P2P service discovery.
|
*
|
* @return boolean value indicating whether operation was successful.
|
*/
|
public boolean stopFind() {
|
synchronized (mLock) {
|
if (!checkSupplicantP2pIfaceAndLogFailure("stopFind")) return false;
|
SupplicantResult<Void> result = new SupplicantResult("stopFind()");
|
try {
|
result.setResult(mISupplicantP2pIface.stopFind());
|
} catch (RemoteException e) {
|
Log.e(TAG, "ISupplicantP2pIface exception: " + e);
|
supplicantServiceDiedHandler();
|
}
|
return result.isSuccess();
|
}
|
}
|
|
|
/**
|
* Flush P2P peer table and state.
|
*
|
* @return boolean value indicating whether operation was successful.
|
*/
|
public boolean flush() {
|
synchronized (mLock) {
|
if (!checkSupplicantP2pIfaceAndLogFailure("flush")) return false;
|
SupplicantResult<Void> result = new SupplicantResult("flush()");
|
try {
|
result.setResult(mISupplicantP2pIface.flush());
|
} catch (RemoteException e) {
|
Log.e(TAG, "ISupplicantP2pIface exception: " + e);
|
supplicantServiceDiedHandler();
|
}
|
return result.isSuccess();
|
}
|
}
|
|
|
/**
|
* This command can be used to flush all services from the
|
* device.
|
*
|
* @return boolean value indicating whether operation was successful.
|
*/
|
public boolean serviceFlush() {
|
synchronized (mLock) {
|
if (!checkSupplicantP2pIfaceAndLogFailure("serviceFlush")) return false;
|
SupplicantResult<Void> result = new SupplicantResult("serviceFlush()");
|
try {
|
result.setResult(mISupplicantP2pIface.flushServices());
|
} catch (RemoteException e) {
|
Log.e(TAG, "ISupplicantP2pIface exception: " + e);
|
supplicantServiceDiedHandler();
|
}
|
return result.isSuccess();
|
}
|
}
|
|
|
/**
|
* Turn on/off power save mode for the interface.
|
*
|
* @param groupIfName Group interface name to use.
|
* @param enable Indicate if power save is to be turned on/off.
|
*
|
* @return boolean value indicating whether operation was successful.
|
*/
|
public boolean setPowerSave(String groupIfName, boolean enable) {
|
synchronized (mLock) {
|
if (!checkSupplicantP2pIfaceAndLogFailure("setPowerSave")) return false;
|
SupplicantResult<Void> result = new SupplicantResult(
|
"setPowerSave(" + groupIfName + ", " + enable + ")");
|
try {
|
result.setResult(mISupplicantP2pIface.setPowerSave(groupIfName, enable));
|
} catch (RemoteException e) {
|
Log.e(TAG, "ISupplicantP2pIface exception: " + e);
|
supplicantServiceDiedHandler();
|
}
|
return result.isSuccess();
|
}
|
}
|
|
|
/**
|
* Set the Maximum idle time in seconds for P2P groups.
|
* This value controls how long a P2P group is maintained after there
|
* is no other members in the group. As a group owner, this means no
|
* associated stations in the group. As a P2P client, this means no
|
* group owner seen in scan results.
|
*
|
* @param groupIfName Group interface name to use.
|
* @param timeoutInSec Timeout value in seconds.
|
*
|
* @return boolean value indicating whether operation was successful.
|
*/
|
public boolean setGroupIdle(String groupIfName, int timeoutInSec) {
|
synchronized (mLock) {
|
if (!checkSupplicantP2pIfaceAndLogFailure("setGroupIdle")) return false;
|
// Basic checking here. Leave actual parameter validation to supplicant.
|
if (timeoutInSec < 0) {
|
Log.e(TAG, "Invalid group timeout value " + timeoutInSec);
|
return false;
|
}
|
|
SupplicantResult<Void> result = new SupplicantResult(
|
"setGroupIdle(" + groupIfName + ", " + timeoutInSec + ")");
|
try {
|
result.setResult(mISupplicantP2pIface.setGroupIdle(groupIfName, timeoutInSec));
|
} catch (RemoteException e) {
|
Log.e(TAG, "ISupplicantP2pIface exception: " + e);
|
supplicantServiceDiedHandler();
|
}
|
return result.isSuccess();
|
}
|
}
|
|
|
/**
|
* Set the postfix to be used for P2P SSID's.
|
*
|
* @param postfix String to be appended to SSID.
|
*
|
* @return boolean value indicating whether operation was successful.
|
*/
|
public boolean setSsidPostfix(String postfix) {
|
synchronized (mLock) {
|
if (!checkSupplicantP2pIfaceAndLogFailure("setSsidPostfix")) return false;
|
// Basic checking here. Leave actual parameter validation to supplicant.
|
if (postfix == null) {
|
Log.e(TAG, "Invalid SSID postfix value (null).");
|
return false;
|
}
|
|
SupplicantResult<Void> result = new SupplicantResult("setSsidPostfix(" + postfix + ")");
|
try {
|
result.setResult(mISupplicantP2pIface.setSsidPostfix(
|
NativeUtil.decodeSsid("\"" + postfix + "\"")));
|
} catch (RemoteException e) {
|
Log.e(TAG, "ISupplicantP2pIface exception: " + e);
|
supplicantServiceDiedHandler();
|
} catch (IllegalArgumentException e) {
|
Log.e(TAG, "Could not decode SSID.", e);
|
return false;
|
}
|
|
return result.isSuccess();
|
}
|
}
|
|
|
/**
|
* Start P2P group formation with a discovered P2P peer. This includes
|
* optional group owner negotiation, group interface setup, provisioning,
|
* and establishing data connection.
|
*
|
* @param config Configuration to use to connect to remote device.
|
* @param joinExistingGroup Indicates that this is a command to join an
|
* existing group as a client. It skips the group owner negotiation
|
* part. This must send a Provision Discovery Request message to the
|
* target group owner before associating for WPS provisioning.
|
*
|
* @return String containing generated pin, if selected provision method
|
* uses PIN.
|
*/
|
public String connect(WifiP2pConfig config, boolean joinExistingGroup) {
|
if (config == null) return null;
|
synchronized (mLock) {
|
if (!checkSupplicantP2pIfaceAndLogFailure("setSsidPostfix")) return null;
|
|
if (config == null) {
|
Log.e(TAG, "Could not connect: null config.");
|
return null;
|
}
|
|
if (config.deviceAddress == null) {
|
Log.e(TAG, "Could not parse null mac address.");
|
return null;
|
}
|
|
if (config.wps.setup == WpsInfo.PBC && !TextUtils.isEmpty(config.wps.pin)) {
|
Log.e(TAG, "Expected empty pin for PBC.");
|
return null;
|
}
|
|
byte[] peerAddress = null;
|
try {
|
peerAddress = NativeUtil.macAddressToByteArray(config.deviceAddress);
|
} catch (Exception e) {
|
Log.e(TAG, "Could not parse peer mac address.", e);
|
return null;
|
}
|
|
int provisionMethod = wpsInfoToConfigMethod(config.wps.setup);
|
if (provisionMethod == RESULT_NOT_VALID) {
|
Log.e(TAG, "Invalid WPS config method: " + config.wps.setup);
|
return null;
|
}
|
// NOTE: preSelectedPin cannot be null, otherwise hal would crash.
|
String preSelectedPin = TextUtils.isEmpty(config.wps.pin) ? "" : config.wps.pin;
|
boolean persistent = (config.netId == WifiP2pGroup.PERSISTENT_NET_ID);
|
|
int goIntent = 0;
|
if (!joinExistingGroup) {
|
int groupOwnerIntent = config.groupOwnerIntent;
|
if (groupOwnerIntent < 0 || groupOwnerIntent > 15) {
|
groupOwnerIntent = DEFAULT_GROUP_OWNER_INTENT;
|
}
|
goIntent = groupOwnerIntent;
|
}
|
|
SupplicantResult<String> result = new SupplicantResult(
|
"connect(" + config.deviceAddress + ")");
|
try {
|
mISupplicantP2pIface.connect(
|
peerAddress, provisionMethod, preSelectedPin, joinExistingGroup,
|
persistent, goIntent,
|
(SupplicantStatus status, String generatedPin) -> {
|
result.setResult(status, generatedPin);
|
});
|
} catch (RemoteException e) {
|
Log.e(TAG, "ISupplicantP2pIface exception: " + e);
|
supplicantServiceDiedHandler();
|
}
|
return result.getResult();
|
}
|
}
|
|
/**
|
* Cancel an ongoing P2P group formation and joining-a-group related
|
* operation. This operation unauthorizes the specific peer device (if any
|
* had been authorized to start group formation), stops P2P find (if in
|
* progress), stops pending operations for join-a-group, and removes the
|
* P2P group interface (if one was used) that is in the WPS provisioning
|
* step. If the WPS provisioning step has been completed, the group is not
|
* terminated.
|
*
|
* @return boolean value indicating whether operation was successful.
|
*/
|
public boolean cancelConnect() {
|
synchronized (mLock) {
|
if (!checkSupplicantP2pIfaceAndLogFailure("cancelConnect")) return false;
|
SupplicantResult<Void> result = new SupplicantResult("cancelConnect()");
|
try {
|
result.setResult(mISupplicantP2pIface.cancelConnect());
|
} catch (RemoteException e) {
|
Log.e(TAG, "ISupplicantP2pIface exception: " + e);
|
supplicantServiceDiedHandler();
|
}
|
return result.isSuccess();
|
}
|
}
|
|
|
/**
|
* Send P2P provision discovery request to the specified peer. The
|
* parameters for this command are the P2P device address of the peer and the
|
* desired configuration method.
|
*
|
* @param config Config class describing peer setup.
|
*
|
* @return boolean value indicating whether operation was successful.
|
*/
|
public boolean provisionDiscovery(WifiP2pConfig config) {
|
if (config == null) return false;
|
synchronized (mLock) {
|
if (!checkSupplicantP2pIfaceAndLogFailure("provisionDiscovery")) return false;
|
|
int targetMethod = wpsInfoToConfigMethod(config.wps.setup);
|
if (targetMethod == RESULT_NOT_VALID) {
|
Log.e(TAG, "Unrecognized WPS configuration method: " + config.wps.setup);
|
return false;
|
}
|
if (targetMethod == ISupplicantP2pIface.WpsProvisionMethod.DISPLAY) {
|
// We are doing display, so provision discovery is keypad.
|
targetMethod = ISupplicantP2pIface.WpsProvisionMethod.KEYPAD;
|
} else if (targetMethod == ISupplicantP2pIface.WpsProvisionMethod.KEYPAD) {
|
// We are doing keypad, so provision discovery is display.
|
targetMethod = ISupplicantP2pIface.WpsProvisionMethod.DISPLAY;
|
}
|
|
if (config.deviceAddress == null) {
|
Log.e(TAG, "Cannot parse null mac address.");
|
return false;
|
}
|
byte[] macAddress = null;
|
try {
|
macAddress = NativeUtil.macAddressToByteArray(config.deviceAddress);
|
} catch (Exception e) {
|
Log.e(TAG, "Could not parse peer mac address.", e);
|
return false;
|
}
|
|
SupplicantResult<Void> result = new SupplicantResult(
|
"provisionDiscovery(" + config.deviceAddress + ", " + config.wps.setup + ")");
|
try {
|
result.setResult(mISupplicantP2pIface.provisionDiscovery(macAddress, targetMethod));
|
} catch (RemoteException e) {
|
Log.e(TAG, "ISupplicantP2pIface exception: " + e);
|
supplicantServiceDiedHandler();
|
}
|
|
return result.isSuccess();
|
}
|
}
|
|
|
/**
|
* Invite a device to a persistent group.
|
* If the peer device is the group owner of the persistent group, the peer
|
* parameter is not needed. Otherwise it is used to specify which
|
* device to invite. |goDeviceAddress| parameter may be used to override
|
* the group owner device address for Invitation Request should it not be
|
* known for some reason (this should not be needed in most cases).
|
*
|
* @param group Group object to use.
|
* @param peerAddress MAC address of the device to invite.
|
*
|
* @return boolean value indicating whether operation was successful.
|
*/
|
public boolean invite(WifiP2pGroup group, String peerAddress) {
|
if (TextUtils.isEmpty(peerAddress)) return false;
|
synchronized (mLock) {
|
if (!checkSupplicantP2pIfaceAndLogFailure("invite")) return false;
|
if (group == null) {
|
Log.e(TAG, "Cannot invite to null group.");
|
return false;
|
}
|
|
if (group.getOwner() == null) {
|
Log.e(TAG, "Cannot invite to group with null owner.");
|
return false;
|
}
|
|
if (group.getOwner().deviceAddress == null) {
|
Log.e(TAG, "Group owner has no mac address.");
|
return false;
|
}
|
|
byte[] ownerMacAddress = null;
|
try {
|
ownerMacAddress = NativeUtil.macAddressToByteArray(group.getOwner().deviceAddress);
|
} catch (Exception e) {
|
Log.e(TAG, "Group owner mac address parse error.", e);
|
return false;
|
}
|
|
if (peerAddress == null) {
|
Log.e(TAG, "Cannot parse peer mac address.");
|
return false;
|
}
|
|
byte[] peerMacAddress;
|
try {
|
peerMacAddress = NativeUtil.macAddressToByteArray(peerAddress);
|
} catch (Exception e) {
|
Log.e(TAG, "Peer mac address parse error.", e);
|
return false;
|
}
|
|
SupplicantResult<Void> result = new SupplicantResult(
|
"invite(" + group.getInterface() + ", " + group.getOwner().deviceAddress
|
+ ", " + peerAddress + ")");
|
try {
|
result.setResult(mISupplicantP2pIface.invite(
|
group.getInterface(), ownerMacAddress, peerMacAddress));
|
} catch (RemoteException e) {
|
Log.e(TAG, "ISupplicantP2pIface exception: " + e);
|
supplicantServiceDiedHandler();
|
}
|
return result.isSuccess();
|
}
|
}
|
|
|
/**
|
* Reject connection attempt from a peer (specified with a device
|
* address). This is a mechanism to reject a pending group owner negotiation
|
* with a peer and request to automatically block any further connection or
|
* discovery of the peer.
|
*
|
* @param peerAddress MAC address of the device to reject.
|
*
|
* @return boolean value indicating whether operation was successful.
|
*/
|
public boolean reject(String peerAddress) {
|
synchronized (mLock) {
|
if (!checkSupplicantP2pIfaceAndLogFailure("reject")) return false;
|
|
if (peerAddress == null) {
|
Log.e(TAG, "Cannot parse rejected peer's mac address.");
|
return false;
|
}
|
byte[] macAddress = null;
|
try {
|
macAddress = NativeUtil.macAddressToByteArray(peerAddress);
|
} catch (Exception e) {
|
Log.e(TAG, "Could not parse peer mac address.", e);
|
return false;
|
}
|
|
SupplicantResult<Void> result =
|
new SupplicantResult("reject(" + peerAddress + ")");
|
try {
|
result.setResult(mISupplicantP2pIface.reject(macAddress));
|
} catch (RemoteException e) {
|
Log.e(TAG, "ISupplicantP2pIface exception: " + e);
|
supplicantServiceDiedHandler();
|
}
|
|
return result.isSuccess();
|
}
|
}
|
|
|
/**
|
* Gets the MAC address of the device.
|
*
|
* @return MAC address of the device.
|
*/
|
public String getDeviceAddress() {
|
synchronized (mLock) {
|
if (!checkSupplicantP2pIfaceAndLogFailure("getDeviceAddress")) return null;
|
SupplicantResult<String> result = new SupplicantResult("getDeviceAddress()");
|
try {
|
mISupplicantP2pIface.getDeviceAddress((SupplicantStatus status, byte[] address) -> {
|
String parsedAddress = null;
|
try {
|
parsedAddress = NativeUtil.macAddressFromByteArray(address);
|
} catch (Exception e) {
|
Log.e(TAG, "Could not process reported address.", e);
|
}
|
result.setResult(status, parsedAddress);
|
});
|
} catch (RemoteException e) {
|
Log.e(TAG, "ISupplicantP2pIface exception: " + e);
|
supplicantServiceDiedHandler();
|
return null;
|
}
|
|
return result.getResult();
|
}
|
}
|
|
|
/**
|
* Gets the operational SSID of the device.
|
*
|
* @param address MAC address of the peer.
|
*
|
* @return SSID of the device.
|
*/
|
public String getSsid(String address) {
|
synchronized (mLock) {
|
if (!checkSupplicantP2pIfaceAndLogFailure("getSsid")) return null;
|
|
if (address == null) {
|
Log.e(TAG, "Cannot parse peer mac address.");
|
return null;
|
}
|
byte[] macAddress = null;
|
try {
|
macAddress = NativeUtil.macAddressToByteArray(address);
|
} catch (Exception e) {
|
Log.e(TAG, "Could not parse mac address.", e);
|
return null;
|
}
|
|
SupplicantResult<String> result =
|
new SupplicantResult("getSsid(" + address + ")");
|
try {
|
mISupplicantP2pIface.getSsid(
|
macAddress, (SupplicantStatus status, ArrayList<Byte> ssid) -> {
|
String ssidString = null;
|
if (ssid != null) {
|
try {
|
ssidString = NativeUtil.removeEnclosingQuotes(
|
NativeUtil.encodeSsid(ssid));
|
} catch (Exception e) {
|
Log.e(TAG, "Could not encode SSID.", e);
|
}
|
}
|
result.setResult(status, ssidString);
|
});
|
} catch (RemoteException e) {
|
Log.e(TAG, "ISupplicantP2pIface exception: " + e);
|
supplicantServiceDiedHandler();
|
return null;
|
}
|
|
return result.getResult();
|
}
|
}
|
|
|
/**
|
* Reinvoke a device from a persistent group.
|
*
|
* @param networkId Used to specify the persistent group.
|
* @param peerAddress MAC address of the device to reinvoke.
|
*
|
* @return true, if operation was successful.
|
*/
|
public boolean reinvoke(int networkId, String peerAddress) {
|
if (TextUtils.isEmpty(peerAddress) || networkId < 0) return false;
|
synchronized (mLock) {
|
if (!checkSupplicantP2pIfaceAndLogFailure("reinvoke")) return false;
|
if (peerAddress == null) {
|
Log.e(TAG, "Cannot parse peer mac address.");
|
return false;
|
}
|
byte[] macAddress = null;
|
try {
|
macAddress = NativeUtil.macAddressToByteArray(peerAddress);
|
} catch (Exception e) {
|
Log.e(TAG, "Could not parse mac address.", e);
|
return false;
|
}
|
|
SupplicantResult<Void> result = new SupplicantResult(
|
"reinvoke(" + networkId + ", " + peerAddress + ")");
|
try {
|
result.setResult(mISupplicantP2pIface.reinvoke(networkId, macAddress));
|
} catch (RemoteException e) {
|
Log.e(TAG, "ISupplicantP2pIface exception: " + e);
|
supplicantServiceDiedHandler();
|
}
|
|
return result.isSuccess();
|
}
|
}
|
|
|
/**
|
* Set up a P2P group owner manually (i.e., without group owner
|
* negotiation with a specific peer). This is also known as autonomous
|
* group owner.
|
*
|
* @param networkId Used to specify the restart of a persistent group.
|
* @param isPersistent Used to request a persistent group to be formed.
|
*
|
* @return true, if operation was successful.
|
*/
|
public boolean groupAdd(int networkId, boolean isPersistent) {
|
synchronized (mLock) {
|
if (!checkSupplicantP2pIfaceAndLogFailure("groupAdd")) return false;
|
SupplicantResult<Void> result =
|
new SupplicantResult("groupAdd(" + networkId + ", " + isPersistent + ")");
|
try {
|
result.setResult(mISupplicantP2pIface.addGroup(isPersistent, networkId));
|
} catch (RemoteException e) {
|
Log.e(TAG, "ISupplicantP2pIface exception: " + e);
|
supplicantServiceDiedHandler();
|
}
|
return result.isSuccess();
|
}
|
}
|
|
/**
|
* Set up a P2P group as Group Owner or join a group with a configuration.
|
*
|
* @param networkName SSID of the group to be formed
|
* @param passphrase passphrase of the group to be formed
|
* @param isPersistent Used to request a persistent group to be formed.
|
* @param freq prefered frequencty or band of the group to be formed
|
* @param peerAddress peerAddress Group Owner MAC address, only applied for Group Client.
|
* If the MAC is "00:00:00:00:00:00", the device will try to find a peer
|
* whose SSID matches ssid.
|
* @param join join a group or create a group
|
*
|
* @return true, if operation was successful.
|
*/
|
public boolean groupAdd(String networkName, String passphrase,
|
boolean isPersistent, int freq, String peerAddress, boolean join) {
|
synchronized (mLock) {
|
if (!checkSupplicantP2pIfaceAndLogFailureV1_2("groupAdd_1_2")) return false;
|
java.util.ArrayList<Byte> ssid = NativeUtil.decodeSsid("\"" + networkName + "\"");
|
byte[] macAddress = null;
|
try {
|
macAddress = NativeUtil.macAddressToByteArray(peerAddress);
|
} catch (Exception e) {
|
Log.e(TAG, "Could not parse mac address.", e);
|
return false;
|
}
|
|
android.hardware.wifi.supplicant.V1_2.ISupplicantP2pIface ifaceV12 =
|
getP2pIfaceMockableV1_2();
|
SupplicantResult<Void> result =
|
new SupplicantResult("groupAdd(" + networkName + ", "
|
+ (TextUtils.isEmpty(passphrase) ? "<Empty>" : "<Non-Empty>")
|
+ ", " + isPersistent + ", " + freq
|
+ ", " + peerAddress + ", " + join + ")");
|
try {
|
result.setResult(ifaceV12.addGroup_1_2(
|
ssid, passphrase, isPersistent, freq, macAddress, join));
|
} catch (RemoteException e) {
|
Log.e(TAG, "ISupplicantP2pIface exception: " + e);
|
supplicantServiceDiedHandler();
|
}
|
return result.isSuccess();
|
}
|
}
|
|
/**
|
* Set up a P2P group owner manually.
|
* This is a helper method that invokes groupAdd(networkId, isPersistent) internally.
|
*
|
* @param isPersistent Used to request a persistent group to be formed.
|
*
|
* @return true, if operation was successful.
|
*/
|
public boolean groupAdd(boolean isPersistent) {
|
// Supplicant expects networkId to be -1 if not supplied.
|
return groupAdd(-1, isPersistent);
|
}
|
|
|
/**
|
* Terminate a P2P group. If a new virtual network interface was used for
|
* the group, it must also be removed. The network interface name of the
|
* group interface is used as a parameter for this command.
|
*
|
* @param groupName Group interface name to use.
|
*
|
* @return true, if operation was successful.
|
*/
|
public boolean groupRemove(String groupName) {
|
if (TextUtils.isEmpty(groupName)) return false;
|
synchronized (mLock) {
|
if (!checkSupplicantP2pIfaceAndLogFailure("groupRemove")) return false;
|
SupplicantResult<Void> result = new SupplicantResult("groupRemove(" + groupName + ")");
|
try {
|
result.setResult(mISupplicantP2pIface.removeGroup(groupName));
|
} catch (RemoteException e) {
|
Log.e(TAG, "ISupplicantP2pIface exception: " + e);
|
supplicantServiceDiedHandler();
|
}
|
return result.isSuccess();
|
}
|
}
|
|
|
/**
|
* Gets the capability of the group which the device is a
|
* member of.
|
*
|
* @param peerAddress MAC address of the peer.
|
*
|
* @return combination of |GroupCapabilityMask| values.
|
*/
|
public int getGroupCapability(String peerAddress) {
|
synchronized (mLock) {
|
if (!checkSupplicantP2pIfaceAndLogFailure("getGroupCapability")) {
|
return RESULT_NOT_VALID;
|
}
|
|
if (peerAddress == null) {
|
Log.e(TAG, "Cannot parse peer mac address.");
|
return RESULT_NOT_VALID;
|
}
|
byte[] macAddress = null;
|
try {
|
macAddress = NativeUtil.macAddressToByteArray(peerAddress);
|
} catch (Exception e) {
|
Log.e(TAG, "Could not parse group address.", e);
|
return RESULT_NOT_VALID;
|
}
|
|
SupplicantResult<Integer> capability = new SupplicantResult(
|
"getGroupCapability(" + peerAddress + ")");
|
try {
|
mISupplicantP2pIface.getGroupCapability(
|
macAddress, (SupplicantStatus status, int cap) -> {
|
capability.setResult(status, cap);
|
});
|
} catch (RemoteException e) {
|
Log.e(TAG, "ISupplicantP2pIface exception: " + e);
|
supplicantServiceDiedHandler();
|
}
|
|
if (!capability.isSuccess()) {
|
return RESULT_NOT_VALID;
|
}
|
|
return capability.getResult();
|
}
|
}
|
|
|
/**
|
* Configure Extended Listen Timing.
|
*
|
* If enabled, listen state must be entered every |intervalInMillis| for at
|
* least |periodInMillis|. Both values have acceptable range of 1-65535
|
* (with interval obviously having to be larger than or equal to duration).
|
* If the P2P module is not idle at the time the Extended Listen Timing
|
* timeout occurs, the Listen State operation must be skipped.
|
*
|
* @param enable Enables or disables listening.
|
* @param periodInMillis Period in milliseconds.
|
* @param intervalInMillis Interval in milliseconds.
|
*
|
* @return true, if operation was successful.
|
*/
|
public boolean configureExtListen(boolean enable, int periodInMillis, int intervalInMillis) {
|
if (enable && intervalInMillis < periodInMillis) {
|
return false;
|
}
|
synchronized (mLock) {
|
if (!checkSupplicantP2pIfaceAndLogFailure("configureExtListen")) return false;
|
|
// If listening is disabled, wpa supplicant expects zeroes.
|
if (!enable) {
|
periodInMillis = 0;
|
intervalInMillis = 0;
|
}
|
|
// Verify that the integers are not negative. Leave actual parameter validation to
|
// supplicant.
|
if (periodInMillis < 0 || intervalInMillis < 0) {
|
Log.e(TAG, "Invalid parameters supplied to configureExtListen: " + periodInMillis
|
+ ", " + intervalInMillis);
|
return false;
|
}
|
|
SupplicantResult<Void> result = new SupplicantResult(
|
"configureExtListen(" + periodInMillis + ", " + intervalInMillis + ")");
|
try {
|
result.setResult(
|
mISupplicantP2pIface.configureExtListen(periodInMillis, intervalInMillis));
|
} catch (RemoteException e) {
|
Log.e(TAG, "ISupplicantP2pIface exception: " + e);
|
supplicantServiceDiedHandler();
|
}
|
|
return result.isSuccess();
|
}
|
}
|
|
|
/**
|
* Set P2P Listen channel and operating chanel.
|
*
|
* @param listenChannel Wifi channel. eg, 1, 6, 11.
|
* @param operatingChannel Wifi channel. eg, 1, 6, 11.
|
*
|
* @return true, if operation was successful.
|
*/
|
public boolean setListenChannel(int listenChannel, int operatingChannel) {
|
synchronized (mLock) {
|
if (!checkSupplicantP2pIfaceAndLogFailure("setListenChannel")) return false;
|
|
if (listenChannel >= 1 && listenChannel <= 11) {
|
SupplicantResult<Void> result = new SupplicantResult(
|
"setListenChannel(" + listenChannel + ", " + DEFAULT_OPERATING_CLASS + ")");
|
try {
|
result.setResult(mISupplicantP2pIface.setListenChannel(
|
listenChannel, DEFAULT_OPERATING_CLASS));
|
} catch (RemoteException e) {
|
Log.e(TAG, "ISupplicantP2pIface exception: " + e);
|
supplicantServiceDiedHandler();
|
}
|
if (!result.isSuccess()) {
|
return false;
|
}
|
} else if (listenChannel != 0) {
|
// listenChannel == 0 does not set any listen channel.
|
return false;
|
}
|
|
if (operatingChannel >= 0 && operatingChannel <= 165) {
|
ArrayList<ISupplicantP2pIface.FreqRange> ranges = new ArrayList<>();
|
// operatingChannel == 0 enables all freqs.
|
if (operatingChannel >= 1 && operatingChannel <= 165) {
|
int freq = (operatingChannel <= 14 ? 2407 : 5000) + operatingChannel * 5;
|
ISupplicantP2pIface.FreqRange range1 = new ISupplicantP2pIface.FreqRange();
|
range1.min = 1000;
|
range1.max = freq - 5;
|
ISupplicantP2pIface.FreqRange range2 = new ISupplicantP2pIface.FreqRange();
|
range2.min = freq + 5;
|
range2.max = 6000;
|
ranges.add(range1);
|
ranges.add(range2);
|
}
|
SupplicantResult<Void> result = new SupplicantResult(
|
"setDisallowedFrequencies(" + ranges + ")");
|
try {
|
result.setResult(mISupplicantP2pIface.setDisallowedFrequencies(ranges));
|
} catch (RemoteException e) {
|
Log.e(TAG, "ISupplicantP2pIface exception: " + e);
|
supplicantServiceDiedHandler();
|
}
|
return result.isSuccess();
|
}
|
return false;
|
}
|
}
|
|
|
/**
|
* This command can be used to add a upnp/bonjour service.
|
*
|
* @param servInfo List of service queries.
|
*
|
* @return true, if operation was successful.
|
*/
|
public boolean serviceAdd(WifiP2pServiceInfo servInfo) {
|
synchronized (mLock) {
|
if (!checkSupplicantP2pIfaceAndLogFailure("serviceAdd")) return false;
|
|
if (servInfo == null) {
|
Log.e(TAG, "Null service info passed.");
|
return false;
|
}
|
|
for (String s : servInfo.getSupplicantQueryList()) {
|
if (s == null) {
|
Log.e(TAG, "Invalid service description (null).");
|
return false;
|
}
|
|
String[] data = s.split(" ");
|
if (data.length < 3) {
|
Log.e(TAG, "Service specification invalid: " + s);
|
return false;
|
}
|
|
SupplicantResult<Void> result = null;
|
try {
|
if ("upnp".equals(data[0])) {
|
int version = 0;
|
try {
|
version = Integer.parseInt(data[1], 16);
|
} catch (NumberFormatException e) {
|
Log.e(TAG, "UPnP Service specification invalid: " + s, e);
|
return false;
|
}
|
|
result = new SupplicantResult(
|
"addUpnpService(" + data[1] + ", " + data[2] + ")");
|
result.setResult(mISupplicantP2pIface.addUpnpService(version, data[2]));
|
} else if ("bonjour".equals(data[0])) {
|
if (data[1] != null && data[2] != null) {
|
ArrayList<Byte> request = null;
|
ArrayList<Byte> response = null;
|
try {
|
request = NativeUtil.byteArrayToArrayList(
|
NativeUtil.hexStringToByteArray(data[1]));
|
response = NativeUtil.byteArrayToArrayList(
|
NativeUtil.hexStringToByteArray(data[2]));
|
} catch (Exception e) {
|
Log.e(TAG, "Invalid bonjour service description.");
|
return false;
|
}
|
result = new SupplicantResult(
|
"addBonjourService(" + data[1] + ", " + data[2] + ")");
|
result.setResult(
|
mISupplicantP2pIface.addBonjourService(request, response));
|
}
|
} else {
|
return false;
|
}
|
} catch (RemoteException e) {
|
Log.e(TAG, "ISupplicantP2pIface exception: " + e);
|
supplicantServiceDiedHandler();
|
}
|
|
if (result == null || !result.isSuccess()) return false;
|
}
|
|
return true;
|
}
|
}
|
|
|
/**
|
* This command can be used to remove a upnp/bonjour service.
|
*
|
* @param servInfo List of service queries.
|
*
|
* @return true, if operation was successful.
|
*/
|
public boolean serviceRemove(WifiP2pServiceInfo servInfo) {
|
synchronized (mLock) {
|
if (!checkSupplicantP2pIfaceAndLogFailure("serviceRemove")) return false;
|
|
if (servInfo == null) {
|
Log.e(TAG, "Null service info passed.");
|
return false;
|
}
|
|
for (String s : servInfo.getSupplicantQueryList()) {
|
if (s == null) {
|
Log.e(TAG, "Invalid service description (null).");
|
return false;
|
}
|
|
String[] data = s.split(" ");
|
if (data.length < 3) {
|
Log.e(TAG, "Service specification invalid: " + s);
|
return false;
|
}
|
|
SupplicantResult<Void> result = null;
|
try {
|
if ("upnp".equals(data[0])) {
|
int version = 0;
|
try {
|
version = Integer.parseInt(data[1], 16);
|
} catch (NumberFormatException e) {
|
Log.e(TAG, "UPnP Service specification invalid: " + s, e);
|
return false;
|
}
|
result = new SupplicantResult(
|
"removeUpnpService(" + data[1] + ", " + data[2] + ")");
|
result.setResult(mISupplicantP2pIface.removeUpnpService(version, data[2]));
|
} else if ("bonjour".equals(data[0])) {
|
if (data[1] != null) {
|
ArrayList<Byte> request = null;
|
try {
|
request = NativeUtil.byteArrayToArrayList(
|
NativeUtil.hexStringToByteArray(data[1]));
|
} catch (Exception e) {
|
Log.e(TAG, "Invalid bonjour service description.");
|
return false;
|
}
|
result = new SupplicantResult("removeBonjourService(" + data[1] + ")");
|
result.setResult(mISupplicantP2pIface.removeBonjourService(request));
|
}
|
} else {
|
Log.e(TAG, "Unknown / unsupported P2P service requested: " + data[0]);
|
return false;
|
}
|
} catch (RemoteException e) {
|
Log.e(TAG, "ISupplicantP2pIface exception: " + e);
|
supplicantServiceDiedHandler();
|
}
|
|
if (result == null || !result.isSuccess()) return false;
|
}
|
|
return true;
|
}
|
}
|
|
|
/**
|
* Schedule a P2P service discovery request. The parameters for this command
|
* are the device address of the peer device (or 00:00:00:00:00:00 for
|
* wildcard query that is sent to every discovered P2P peer that supports
|
* service discovery) and P2P Service Query TLV(s) as hexdump.
|
*
|
* @param peerAddress MAC address of the device to discover.
|
* @param query Hex dump of the query data.
|
* @return identifier Identifier for the request. Can be used to cancel the
|
* request.
|
*/
|
public String requestServiceDiscovery(String peerAddress, String query) {
|
synchronized (mLock) {
|
if (!checkSupplicantP2pIfaceAndLogFailure("requestServiceDiscovery")) return null;
|
|
if (peerAddress == null) {
|
Log.e(TAG, "Cannot parse peer mac address.");
|
return null;
|
}
|
byte[] macAddress = null;
|
try {
|
macAddress = NativeUtil.macAddressToByteArray(peerAddress);
|
} catch (Exception e) {
|
Log.e(TAG, "Could not process peer MAC address.", e);
|
return null;
|
}
|
|
if (query == null) {
|
Log.e(TAG, "Cannot parse service discovery query: " + query);
|
return null;
|
}
|
ArrayList<Byte> binQuery = null;
|
try {
|
binQuery = NativeUtil.byteArrayToArrayList(NativeUtil.hexStringToByteArray(query));
|
} catch (Exception e) {
|
Log.e(TAG, "Could not parse service query.", e);
|
return null;
|
}
|
|
SupplicantResult<Long> result = new SupplicantResult(
|
"requestServiceDiscovery(" + peerAddress + ", " + query + ")");
|
try {
|
mISupplicantP2pIface.requestServiceDiscovery(
|
macAddress, binQuery,
|
(SupplicantStatus status, long identifier) -> {
|
result.setResult(status, new Long(identifier));
|
});
|
} catch (RemoteException e) {
|
Log.e(TAG, "ISupplicantP2pIface exception: " + e);
|
supplicantServiceDiedHandler();
|
}
|
|
Long value = result.getResult();
|
if (value == null) return null;
|
return value.toString();
|
}
|
}
|
|
|
/**
|
* Cancel a previous service discovery request.
|
*
|
* @param identifier Identifier for the request to cancel.
|
* @return true, if operation was successful.
|
*/
|
public boolean cancelServiceDiscovery(String identifier) {
|
synchronized (mLock) {
|
if (!checkSupplicantP2pIfaceAndLogFailure("cancelServiceDiscovery")) return false;
|
if (identifier == null) {
|
Log.e(TAG, "cancelServiceDiscovery requires a valid tag.");
|
return false;
|
}
|
|
long id = 0;
|
try {
|
id = Long.parseLong(identifier);
|
} catch (NumberFormatException e) {
|
Log.e(TAG, "Service discovery identifier invalid: " + identifier, e);
|
return false;
|
}
|
|
SupplicantResult<Void> result = new SupplicantResult(
|
"cancelServiceDiscovery(" + identifier + ")");
|
try {
|
result.setResult(mISupplicantP2pIface.cancelServiceDiscovery(id));
|
} catch (RemoteException e) {
|
Log.e(TAG, "ISupplicantP2pIface exception: " + e);
|
supplicantServiceDiedHandler();
|
}
|
|
return result.isSuccess();
|
}
|
}
|
|
|
/**
|
* Send driver command to set Miracast mode.
|
*
|
* @param mode Mode of Miracast.
|
* @return true, if operation was successful.
|
*/
|
public boolean setMiracastMode(int mode) {
|
synchronized (mLock) {
|
if (!checkSupplicantP2pIfaceAndLogFailure("setMiracastMode")) return false;
|
byte targetMode = ISupplicantP2pIface.MiracastMode.DISABLED;
|
|
switch (mode) {
|
case WifiP2pManager.MIRACAST_SOURCE:
|
targetMode = ISupplicantP2pIface.MiracastMode.SOURCE;
|
break;
|
|
case WifiP2pManager.MIRACAST_SINK:
|
targetMode = ISupplicantP2pIface.MiracastMode.SINK;
|
break;
|
}
|
|
SupplicantResult<Void> result = new SupplicantResult(
|
"setMiracastMode(" + mode + ")");
|
try {
|
result.setResult(mISupplicantP2pIface.setMiracastMode(targetMode));
|
} catch (RemoteException e) {
|
Log.e(TAG, "ISupplicantP2pIface exception: " + e);
|
supplicantServiceDiedHandler();
|
}
|
|
return result.isSuccess();
|
}
|
}
|
|
|
/**
|
* Initiate WPS Push Button setup.
|
* The PBC operation requires that a button is also pressed at the
|
* AP/Registrar at about the same time (2 minute window).
|
*
|
* @param groupIfName Group interface name to use.
|
* @param bssid BSSID of the AP. Use empty bssid to indicate wildcard.
|
* @return true, if operation was successful.
|
*/
|
public boolean startWpsPbc(String groupIfName, String bssid) {
|
if (TextUtils.isEmpty(groupIfName)) {
|
Log.e(TAG, "Group name required when requesting WPS PBC. Got (" + groupIfName + ")");
|
return false;
|
}
|
synchronized (mLock) {
|
if (!checkSupplicantP2pIfaceAndLogFailure("startWpsPbc")) return false;
|
// Null values should be fine, since bssid can be empty.
|
byte[] macAddress = null;
|
try {
|
macAddress = NativeUtil.macAddressToByteArray(bssid);
|
} catch (Exception e) {
|
Log.e(TAG, "Could not parse BSSID.", e);
|
return false;
|
}
|
|
SupplicantResult<Void> result = new SupplicantResult(
|
"startWpsPbc(" + groupIfName + ", " + bssid + ")");
|
try {
|
result.setResult(mISupplicantP2pIface.startWpsPbc(groupIfName, macAddress));
|
} catch (RemoteException e) {
|
Log.e(TAG, "ISupplicantP2pIface exception: " + e);
|
supplicantServiceDiedHandler();
|
}
|
|
return result.isSuccess();
|
}
|
}
|
|
|
/**
|
* Initiate WPS Pin Keypad setup.
|
*
|
* @param groupIfName Group interface name to use.
|
* @param pin 8 digit pin to be used.
|
* @return true, if operation was successful.
|
*/
|
public boolean startWpsPinKeypad(String groupIfName, String pin) {
|
if (TextUtils.isEmpty(groupIfName) || TextUtils.isEmpty(pin)) return false;
|
synchronized (mLock) {
|
if (!checkSupplicantP2pIfaceAndLogFailure("startWpsPinKeypad")) return false;
|
if (groupIfName == null) {
|
Log.e(TAG, "Group name required when requesting WPS KEYPAD.");
|
return false;
|
}
|
if (pin == null) {
|
Log.e(TAG, "PIN required when requesting WPS KEYPAD.");
|
return false;
|
}
|
|
SupplicantResult<Void> result = new SupplicantResult(
|
"startWpsPinKeypad(" + groupIfName + ", " + pin + ")");
|
try {
|
result.setResult(mISupplicantP2pIface.startWpsPinKeypad(groupIfName, pin));
|
} catch (RemoteException e) {
|
Log.e(TAG, "ISupplicantP2pIface exception: " + e);
|
supplicantServiceDiedHandler();
|
}
|
|
return result.isSuccess();
|
}
|
}
|
|
|
/**
|
* Initiate WPS Pin Display setup.
|
*
|
* @param groupIfName Group interface name to use.
|
* @param bssid BSSID of the AP. Use empty bssid to indicate wildcard.
|
* @return generated pin if operation was successful, null otherwise.
|
*/
|
public String startWpsPinDisplay(String groupIfName, String bssid) {
|
if (TextUtils.isEmpty(groupIfName)) return null;
|
synchronized (mLock) {
|
if (!checkSupplicantP2pIfaceAndLogFailure("startWpsPinDisplay")) return null;
|
if (groupIfName == null) {
|
Log.e(TAG, "Group name required when requesting WPS KEYPAD.");
|
return null;
|
}
|
|
// Null values should be fine, since bssid can be empty.
|
byte[] macAddress = null;
|
try {
|
macAddress = NativeUtil.macAddressToByteArray(bssid);
|
} catch (Exception e) {
|
Log.e(TAG, "Could not parse BSSID.", e);
|
return null;
|
}
|
|
SupplicantResult<String> result = new SupplicantResult(
|
"startWpsPinDisplay(" + groupIfName + ", " + bssid + ")");
|
try {
|
mISupplicantP2pIface.startWpsPinDisplay(
|
groupIfName, macAddress,
|
(SupplicantStatus status, String generatedPin) -> {
|
result.setResult(status, generatedPin);
|
});
|
} catch (RemoteException e) {
|
Log.e(TAG, "ISupplicantP2pIface exception: " + e);
|
supplicantServiceDiedHandler();
|
}
|
|
return result.getResult();
|
}
|
}
|
|
|
/**
|
* Cancel any ongoing WPS operations.
|
*
|
* @param groupIfName Group interface name to use.
|
* @return true, if operation was successful.
|
*/
|
public boolean cancelWps(String groupIfName) {
|
synchronized (mLock) {
|
if (!checkSupplicantP2pIfaceAndLogFailure("cancelWps")) return false;
|
if (groupIfName == null) {
|
Log.e(TAG, "Group name required when requesting WPS KEYPAD.");
|
return false;
|
}
|
|
SupplicantResult<Void> result = new SupplicantResult(
|
"cancelWps(" + groupIfName + ")");
|
try {
|
result.setResult(mISupplicantP2pIface.cancelWps(groupIfName));
|
} catch (RemoteException e) {
|
Log.e(TAG, "ISupplicantP2pIface exception: " + e);
|
supplicantServiceDiedHandler();
|
}
|
|
return result.isSuccess();
|
}
|
}
|
|
|
/**
|
* Enable/Disable Wifi Display.
|
*
|
* @param enable true to enable, false to disable.
|
* @return true, if operation was successful.
|
*/
|
public boolean enableWfd(boolean enable) {
|
synchronized (mLock) {
|
if (!checkSupplicantP2pIfaceAndLogFailure("enableWfd")) return false;
|
|
SupplicantResult<Void> result = new SupplicantResult(
|
"enableWfd(" + enable + ")");
|
try {
|
result.setResult(mISupplicantP2pIface.enableWfd(enable));
|
} catch (RemoteException e) {
|
Log.e(TAG, "ISupplicantP2pIface exception: " + e);
|
supplicantServiceDiedHandler();
|
}
|
|
return result.isSuccess();
|
}
|
}
|
|
|
/**
|
* Set Wifi Display device info.
|
*
|
* @param info WFD device info as described in section 5.1.2 of WFD technical
|
* specification v1.0.0.
|
* @return true, if operation was successful.
|
*/
|
public boolean setWfdDeviceInfo(String info) {
|
synchronized (mLock) {
|
if (!checkSupplicantP2pIfaceAndLogFailure("setWfdDeviceInfo")) return false;
|
|
if (info == null) {
|
Log.e(TAG, "Cannot parse null WFD info string.");
|
return false;
|
}
|
byte[] wfdInfo = null;
|
try {
|
wfdInfo = NativeUtil.hexStringToByteArray(info);
|
} catch (Exception e) {
|
Log.e(TAG, "Could not parse WFD Device Info string.");
|
return false;
|
}
|
|
SupplicantResult<Void> result = new SupplicantResult(
|
"setWfdDeviceInfo(" + info + ")");
|
try {
|
result.setResult(mISupplicantP2pIface.setWfdDeviceInfo(wfdInfo));
|
} catch (RemoteException e) {
|
Log.e(TAG, "ISupplicantP2pIface exception: " + e);
|
supplicantServiceDiedHandler();
|
}
|
|
return result.isSuccess();
|
}
|
}
|
|
/**
|
* Remove network with provided id.
|
*
|
* @param networkId Id of the network to lookup.
|
* @return true, if operation was successful.
|
*/
|
public boolean removeNetwork(int networkId) {
|
synchronized (mLock) {
|
if (!checkSupplicantP2pIfaceAndLogFailure("removeNetwork")) return false;
|
|
SupplicantResult<Void> result = new SupplicantResult(
|
"removeNetwork(" + networkId + ")");
|
try {
|
result.setResult(mISupplicantP2pIface.removeNetwork(networkId));
|
} catch (RemoteException e) {
|
Log.e(TAG, "ISupplicantP2pIface exception: " + e);
|
supplicantServiceDiedHandler();
|
}
|
|
return result.isSuccess();
|
}
|
}
|
|
/**
|
* List the networks saved in wpa_supplicant.
|
*
|
* @return List of network ids.
|
*/
|
private List<Integer> listNetworks() {
|
synchronized (mLock) {
|
if (!checkSupplicantP2pIfaceAndLogFailure("listNetworks")) return null;
|
SupplicantResult<ArrayList> result = new SupplicantResult("listNetworks()");
|
try {
|
mISupplicantP2pIface.listNetworks(
|
(SupplicantStatus status, ArrayList<Integer> networkIds) -> {
|
result.setResult(status, networkIds);
|
});
|
} catch (RemoteException e) {
|
Log.e(TAG, "ISupplicantP2pIface exception: " + e);
|
supplicantServiceDiedHandler();
|
}
|
return result.getResult();
|
}
|
}
|
|
/**
|
* Get the supplicant P2p network object for the specified network ID.
|
*
|
* @param networkId Id of the network to lookup.
|
* @return ISupplicantP2pNetwork instance on success, null on failure.
|
*/
|
private ISupplicantP2pNetwork getNetwork(int networkId) {
|
synchronized (mLock) {
|
if (!checkSupplicantP2pIfaceAndLogFailure("getNetwork")) return null;
|
SupplicantResult<ISupplicantNetwork> result =
|
new SupplicantResult("getNetwork(" + networkId + ")");
|
try {
|
mISupplicantP2pIface.getNetwork(
|
networkId,
|
(SupplicantStatus status, ISupplicantNetwork network) -> {
|
result.setResult(status, network);
|
});
|
} catch (RemoteException e) {
|
Log.e(TAG, "ISupplicantP2pIface exception: " + e);
|
supplicantServiceDiedHandler();
|
}
|
if (result.getResult() == null) {
|
Log.e(TAG, "getNetwork got null network");
|
return null;
|
}
|
return getP2pNetworkMockable(result.getResult());
|
}
|
}
|
|
/**
|
* Get the persistent group list from wpa_supplicant's p2p mgmt interface
|
*
|
* @param groups WifiP2pGroupList to store persistent groups in
|
* @return true, if list has been modified.
|
*/
|
public boolean loadGroups(WifiP2pGroupList groups) {
|
synchronized (mLock) {
|
if (!checkSupplicantP2pIfaceAndLogFailure("loadGroups")) return false;
|
List<Integer> networkIds = listNetworks();
|
if (networkIds == null || networkIds.isEmpty()) {
|
return false;
|
}
|
for (Integer networkId : networkIds) {
|
ISupplicantP2pNetwork network = getNetwork(networkId);
|
if (network == null) {
|
Log.e(TAG, "Failed to retrieve network object for " + networkId);
|
continue;
|
}
|
SupplicantResult<Boolean> resultIsCurrent =
|
new SupplicantResult("isCurrent(" + networkId + ")");
|
try {
|
network.isCurrent(
|
(SupplicantStatus status, boolean isCurrent) -> {
|
resultIsCurrent.setResult(status, isCurrent);
|
});
|
} catch (RemoteException e) {
|
Log.e(TAG, "ISupplicantP2pIface exception: " + e);
|
supplicantServiceDiedHandler();
|
}
|
/** Skip the current network, if we're somehow getting networks from the p2p GO
|
interface, instead of p2p mgmt interface*/
|
if (!resultIsCurrent.isSuccess() || resultIsCurrent.getResult()) {
|
Log.i(TAG, "Skipping current network");
|
continue;
|
}
|
|
WifiP2pGroup group = new WifiP2pGroup();
|
group.setNetworkId(networkId);
|
|
// Now get the ssid, bssid and other flags for this network.
|
SupplicantResult<ArrayList> resultSsid =
|
new SupplicantResult("getSsid(" + networkId + ")");
|
try {
|
network.getSsid(
|
(SupplicantStatus status, ArrayList<Byte> ssid) -> {
|
resultSsid.setResult(status, ssid);
|
});
|
} catch (RemoteException e) {
|
Log.e(TAG, "ISupplicantP2pIface exception: " + e);
|
supplicantServiceDiedHandler();
|
}
|
if (resultSsid.isSuccess() && resultSsid.getResult() != null
|
&& !resultSsid.getResult().isEmpty()) {
|
group.setNetworkName(NativeUtil.removeEnclosingQuotes(
|
NativeUtil.encodeSsid(resultSsid.getResult())));
|
}
|
|
SupplicantResult<byte[]> resultBssid =
|
new SupplicantResult("getBssid(" + networkId + ")");
|
try {
|
network.getBssid(
|
(SupplicantStatus status, byte[] bssid) -> {
|
resultBssid.setResult(status, bssid);
|
});
|
} catch (RemoteException e) {
|
Log.e(TAG, "ISupplicantP2pIface exception: " + e);
|
supplicantServiceDiedHandler();
|
}
|
if (resultBssid.isSuccess() && !ArrayUtils.isEmpty(resultBssid.getResult())) {
|
WifiP2pDevice device = new WifiP2pDevice();
|
device.deviceAddress =
|
NativeUtil.macAddressFromByteArray(resultBssid.getResult());
|
group.setOwner(device);
|
}
|
|
SupplicantResult<Boolean> resultIsGo =
|
new SupplicantResult("isGo(" + networkId + ")");
|
try {
|
network.isGo(
|
(SupplicantStatus status, boolean isGo) -> {
|
resultIsGo.setResult(status, isGo);
|
});
|
} catch (RemoteException e) {
|
Log.e(TAG, "ISupplicantP2pIface exception: " + e);
|
supplicantServiceDiedHandler();
|
}
|
if (resultIsGo.isSuccess()) {
|
group.setIsGroupOwner(resultIsGo.getResult());
|
}
|
groups.add(group);
|
}
|
}
|
return true;
|
}
|
|
/**
|
* Set WPS device name.
|
*
|
* @param name String to be set.
|
* @return true if request is sent successfully, false otherwise.
|
*/
|
public boolean setWpsDeviceName(String name) {
|
if (name == null) {
|
return false;
|
}
|
synchronized (mLock) {
|
if (!checkSupplicantP2pIfaceAndLogFailure("setWpsDeviceName")) return false;
|
SupplicantResult<Void> result = new SupplicantResult(
|
"setWpsDeviceName(" + name + ")");
|
try {
|
result.setResult(mISupplicantP2pIface.setWpsDeviceName(name));
|
} catch (RemoteException e) {
|
Log.e(TAG, "ISupplicantP2pIface exception: " + e);
|
supplicantServiceDiedHandler();
|
}
|
return result.isSuccess();
|
}
|
}
|
|
/**
|
* Set WPS device type.
|
*
|
* @param typeStr Type specified as a string. Used format: <categ>-<OUI>-<subcateg>
|
* @return true if request is sent successfully, false otherwise.
|
*/
|
public boolean setWpsDeviceType(String typeStr) {
|
try {
|
Matcher match = WPS_DEVICE_TYPE_PATTERN.matcher(typeStr);
|
if (!match.find() || match.groupCount() != 3) {
|
Log.e(TAG, "Malformed WPS device type " + typeStr);
|
return false;
|
}
|
short categ = Short.parseShort(match.group(1));
|
byte[] oui = NativeUtil.hexStringToByteArray(match.group(2));
|
short subCateg = Short.parseShort(match.group(3));
|
|
byte[] bytes = new byte[8];
|
ByteBuffer byteBuffer = ByteBuffer.wrap(bytes).order(ByteOrder.BIG_ENDIAN);
|
byteBuffer.putShort(categ);
|
byteBuffer.put(oui);
|
byteBuffer.putShort(subCateg);
|
synchronized (mLock) {
|
if (!checkSupplicantP2pIfaceAndLogFailure("setWpsDeviceType")) return false;
|
SupplicantResult<Void> result = new SupplicantResult(
|
"setWpsDeviceType(" + typeStr + ")");
|
try {
|
result.setResult(mISupplicantP2pIface.setWpsDeviceType(bytes));
|
} catch (RemoteException e) {
|
Log.e(TAG, "ISupplicantP2pIface exception: " + e);
|
supplicantServiceDiedHandler();
|
}
|
return result.isSuccess();
|
}
|
} catch (IllegalArgumentException e) {
|
Log.e(TAG, "Illegal argument " + typeStr, e);
|
return false;
|
}
|
}
|
|
/**
|
* Set WPS config methods
|
*
|
* @param configMethodsStr List of config methods.
|
* @return true if request is sent successfully, false otherwise.
|
*/
|
public boolean setWpsConfigMethods(String configMethodsStr) {
|
synchronized (mLock) {
|
if (!checkSupplicantP2pIfaceAndLogFailure("setWpsConfigMethods")) return false;
|
SupplicantResult<Void> result =
|
new SupplicantResult("setWpsConfigMethods(" + configMethodsStr + ")");
|
short configMethodsMask = 0;
|
String[] configMethodsStrArr = configMethodsStr.split("\\s+");
|
for (int i = 0; i < configMethodsStrArr.length; i++) {
|
configMethodsMask |= stringToWpsConfigMethod(configMethodsStrArr[i]);
|
}
|
try {
|
result.setResult(mISupplicantP2pIface.setWpsConfigMethods(configMethodsMask));
|
} catch (RemoteException e) {
|
Log.e(TAG, "ISupplicantP2pIface exception: " + e);
|
supplicantServiceDiedHandler();
|
}
|
return result.isSuccess();
|
}
|
}
|
|
/**
|
* Get NFC handover request message.
|
*
|
* @return select message if created successfully, null otherwise.
|
*/
|
public String getNfcHandoverRequest() {
|
synchronized (mLock) {
|
if (!checkSupplicantP2pIfaceAndLogFailure("getNfcHandoverRequest")) return null;
|
SupplicantResult<ArrayList> result = new SupplicantResult(
|
"getNfcHandoverRequest()");
|
try {
|
mISupplicantP2pIface.createNfcHandoverRequestMessage(
|
(SupplicantStatus status, ArrayList<Byte> message) -> {
|
result.setResult(status, message);
|
});
|
} catch (RemoteException e) {
|
Log.e(TAG, "ISupplicantP2pIface exception: " + e);
|
supplicantServiceDiedHandler();
|
}
|
if (!result.isSuccess()) {
|
return null;
|
|
}
|
return NativeUtil.hexStringFromByteArray(
|
NativeUtil.byteArrayFromArrayList(result.getResult()));
|
}
|
}
|
|
/**
|
* Get NFC handover select message.
|
*
|
* @return select message if created successfully, null otherwise.
|
*/
|
public String getNfcHandoverSelect() {
|
synchronized (mLock) {
|
if (!checkSupplicantP2pIfaceAndLogFailure("getNfcHandoverSelect")) return null;
|
SupplicantResult<ArrayList> result = new SupplicantResult(
|
"getNfcHandoverSelect()");
|
try {
|
mISupplicantP2pIface.createNfcHandoverSelectMessage(
|
(SupplicantStatus status, ArrayList<Byte> message) -> {
|
result.setResult(status, message);
|
});
|
} catch (RemoteException e) {
|
Log.e(TAG, "ISupplicantP2pIface exception: " + e);
|
supplicantServiceDiedHandler();
|
}
|
if (!result.isSuccess()) {
|
return null;
|
|
}
|
return NativeUtil.hexStringFromByteArray(
|
NativeUtil.byteArrayFromArrayList(result.getResult()));
|
}
|
}
|
|
/**
|
* Report NFC handover select message.
|
*
|
* @return true if reported successfully, false otherwise.
|
*/
|
public boolean initiatorReportNfcHandover(String selectMessage) {
|
if (selectMessage == null) return false;
|
synchronized (mLock) {
|
if (!checkSupplicantP2pIfaceAndLogFailure("initiatorReportNfcHandover")) return false;
|
SupplicantResult<Void> result = new SupplicantResult(
|
"initiatorReportNfcHandover(" + selectMessage + ")");
|
try {
|
result.setResult(mISupplicantP2pIface.reportNfcHandoverInitiation(
|
NativeUtil.byteArrayToArrayList(NativeUtil.hexStringToByteArray(
|
selectMessage))));
|
} catch (RemoteException e) {
|
Log.e(TAG, "ISupplicantP2pIface exception: " + e);
|
supplicantServiceDiedHandler();
|
} catch (IllegalArgumentException e) {
|
Log.e(TAG, "Illegal argument " + selectMessage, e);
|
return false;
|
}
|
return result.isSuccess();
|
}
|
}
|
|
/**
|
* Report NFC handover request message.
|
*
|
* @return true if reported successfully, false otherwise.
|
*/
|
public boolean responderReportNfcHandover(String requestMessage) {
|
if (requestMessage == null) return false;
|
synchronized (mLock) {
|
if (!checkSupplicantP2pIfaceAndLogFailure("responderReportNfcHandover")) return false;
|
SupplicantResult<Void> result = new SupplicantResult(
|
"responderReportNfcHandover(" + requestMessage + ")");
|
try {
|
result.setResult(mISupplicantP2pIface.reportNfcHandoverResponse(
|
NativeUtil.byteArrayToArrayList(NativeUtil.hexStringToByteArray(
|
requestMessage))));
|
} catch (RemoteException e) {
|
Log.e(TAG, "ISupplicantP2pIface exception: " + e);
|
supplicantServiceDiedHandler();
|
} catch (IllegalArgumentException e) {
|
Log.e(TAG, "Illegal argument " + requestMessage, e);
|
return false;
|
}
|
return result.isSuccess();
|
}
|
}
|
|
/**
|
* Set the client list for the provided network.
|
*
|
* @param networkId Id of the network.
|
* @param clientListStr Space separated list of clients.
|
* @return true, if operation was successful.
|
*/
|
public boolean setClientList(int networkId, String clientListStr) {
|
synchronized (mLock) {
|
if (!checkSupplicantP2pIfaceAndLogFailure("setClientList")) return false;
|
if (TextUtils.isEmpty(clientListStr)) {
|
Log.e(TAG, "Invalid client list");
|
return false;
|
}
|
ISupplicantP2pNetwork network = getNetwork(networkId);
|
if (network == null) {
|
Log.e(TAG, "Invalid network id ");
|
return false;
|
}
|
SupplicantResult<Void> result = new SupplicantResult(
|
"setClientList(" + networkId + ", " + clientListStr + ")");
|
try {
|
ArrayList<byte[]> clients = new ArrayList<>();
|
for (String clientStr : Arrays.asList(clientListStr.split("\\s+"))) {
|
clients.add(NativeUtil.macAddressToByteArray(clientStr));
|
}
|
result.setResult(network.setClientList(clients));
|
} catch (RemoteException e) {
|
Log.e(TAG, "ISupplicantP2pIface exception: " + e);
|
supplicantServiceDiedHandler();
|
} catch (IllegalArgumentException e) {
|
Log.e(TAG, "Illegal argument " + clientListStr, e);
|
return false;
|
}
|
return result.isSuccess();
|
}
|
}
|
|
/**
|
* Set the client list for the provided network.
|
*
|
* @param networkId Id of the network.
|
* @return Space separated list of clients if successfull, null otherwise.
|
*/
|
public String getClientList(int networkId) {
|
synchronized (mLock) {
|
if (!checkSupplicantP2pIfaceAndLogFailure("getClientList")) return null;
|
ISupplicantP2pNetwork network = getNetwork(networkId);
|
if (network == null) {
|
Log.e(TAG, "Invalid network id ");
|
return null;
|
}
|
SupplicantResult<ArrayList> result = new SupplicantResult(
|
"getClientList(" + networkId + ")");
|
try {
|
network.getClientList(
|
(SupplicantStatus status, ArrayList<byte[]> clients) -> {
|
result.setResult(status, clients);
|
});
|
} catch (RemoteException e) {
|
Log.e(TAG, "ISupplicantP2pIface exception: " + e);
|
supplicantServiceDiedHandler();
|
}
|
if (!result.isSuccess()) {
|
return null;
|
}
|
ArrayList<byte[]> clients = result.getResult();
|
return clients.stream()
|
.map(NativeUtil::macAddressFromByteArray)
|
.collect(Collectors.joining(" "));
|
}
|
}
|
|
/**
|
* Persist the current configurations to disk.
|
*
|
* @return true, if operation was successful.
|
*/
|
public boolean saveConfig() {
|
synchronized (mLock) {
|
if (!checkSupplicantP2pIfaceAndLogFailure("saveConfig")) return false;
|
SupplicantResult<Void> result = new SupplicantResult("saveConfig()");
|
try {
|
result.setResult(mISupplicantP2pIface.saveConfig());
|
} catch (RemoteException e) {
|
Log.e(TAG, "ISupplicantP2pIface exception: " + e);
|
supplicantServiceDiedHandler();
|
}
|
return result.isSuccess();
|
}
|
}
|
|
|
/**
|
* Enable/Disable P2P MAC randomization.
|
*
|
* @param enable true to enable, false to disable.
|
* @return true, if operation was successful.
|
*/
|
public boolean setMacRandomization(boolean enable) {
|
synchronized (mLock) {
|
if (!checkSupplicantP2pIfaceAndLogFailureV1_2("setMacRandomization")) return false;
|
|
android.hardware.wifi.supplicant.V1_2.ISupplicantP2pIface ifaceV12 =
|
getP2pIfaceMockableV1_2();
|
SupplicantResult<Void> result = new SupplicantResult(
|
"setMacRandomization(" + enable + ")");
|
try {
|
result.setResult(ifaceV12.setMacRandomization(enable));
|
} catch (RemoteException e) {
|
Log.e(TAG, "ISupplicantP2pIface exception: " + e);
|
supplicantServiceDiedHandler();
|
}
|
|
return result.isSuccess();
|
}
|
}
|
|
|
/**
|
* Converts the Wps config method string to the equivalent enum value.
|
*/
|
private static short stringToWpsConfigMethod(String configMethod) {
|
switch (configMethod) {
|
case "usba":
|
return WpsConfigMethods.USBA;
|
case "ethernet":
|
return WpsConfigMethods.ETHERNET;
|
case "label":
|
return WpsConfigMethods.LABEL;
|
case "display":
|
return WpsConfigMethods.DISPLAY;
|
case "int_nfc_token":
|
return WpsConfigMethods.INT_NFC_TOKEN;
|
case "ext_nfc_token":
|
return WpsConfigMethods.EXT_NFC_TOKEN;
|
case "nfc_interface":
|
return WpsConfigMethods.NFC_INTERFACE;
|
case "push_button":
|
return WpsConfigMethods.PUSHBUTTON;
|
case "keypad":
|
return WpsConfigMethods.KEYPAD;
|
case "virtual_push_button":
|
return WpsConfigMethods.VIRT_PUSHBUTTON;
|
case "physical_push_button":
|
return WpsConfigMethods.PHY_PUSHBUTTON;
|
case "p2ps":
|
return WpsConfigMethods.P2PS;
|
case "virtual_display":
|
return WpsConfigMethods.VIRT_DISPLAY;
|
case "physical_display":
|
return WpsConfigMethods.PHY_DISPLAY;
|
default:
|
throw new IllegalArgumentException(
|
"Invalid WPS config method: " + configMethod);
|
}
|
}
|
|
/** Container class allowing propagation of status and/or value
|
* from callbacks.
|
*
|
* Primary purpose is to allow callback lambdas to provide results
|
* to parent methods.
|
*/
|
private static class SupplicantResult<E> {
|
private String mMethodName;
|
private SupplicantStatus mStatus;
|
private E mValue;
|
|
SupplicantResult(String methodName) {
|
mMethodName = methodName;
|
mStatus = null;
|
mValue = null;
|
logd("entering " + mMethodName);
|
}
|
|
public void setResult(SupplicantStatus status, E value) {
|
logCompletion(mMethodName, status);
|
logd("leaving " + mMethodName + " with result = " + value);
|
mStatus = status;
|
mValue = value;
|
}
|
|
public void setResult(SupplicantStatus status) {
|
logCompletion(mMethodName, status);
|
logd("leaving " + mMethodName);
|
mStatus = status;
|
}
|
|
public boolean isSuccess() {
|
return (mStatus != null
|
&& (mStatus.code == SupplicantStatusCode.SUCCESS
|
|| mStatus.code == SupplicantStatusCode.FAILURE_IFACE_EXISTS));
|
}
|
|
public E getResult() {
|
return (isSuccess() ? mValue : null);
|
}
|
}
|
}
|