blob: 3302100c6717d0ad94c079029719f6fe285120ec [file] [log] [blame]
/*
* Copyright (C) 2021 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.DebugLevel;
import android.hardware.wifi.supplicant.FreqRange;
import android.hardware.wifi.supplicant.ISupplicant;
import android.hardware.wifi.supplicant.ISupplicantP2pIface;
import android.hardware.wifi.supplicant.ISupplicantP2pIfaceCallback;
import android.hardware.wifi.supplicant.ISupplicantP2pNetwork;
import android.hardware.wifi.supplicant.IfaceInfo;
import android.hardware.wifi.supplicant.IfaceType;
import android.hardware.wifi.supplicant.MiracastMode;
import android.hardware.wifi.supplicant.WpsConfigMethods;
import android.hardware.wifi.supplicant.WpsProvisionMethod;
import android.net.wifi.CoexUnsafeChannel;
import android.net.wifi.ScanResult;
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.IBinder;
import android.os.IBinder.DeathRecipient;
import android.os.RemoteException;
import android.os.ServiceManager;
import android.os.ServiceSpecificException;
import android.text.TextUtils;
import android.util.Log;
import com.android.internal.annotations.VisibleForTesting;
import com.android.modules.utils.build.SdkLevel;
import com.android.server.wifi.util.ArrayUtils;
import com.android.server.wifi.util.NativeUtil;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.util.ArrayList;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* Native calls sending requests to the P2P Hals, and callbacks for receiving P2P events
*/
public class SupplicantP2pIfaceHalAidlImpl implements ISupplicantP2pIfaceHal {
private static final String TAG = "SupplicantP2pIfaceHalAidlImpl";
@VisibleForTesting
private static final String HAL_INSTANCE_NAME = ISupplicant.DESCRIPTOR + "/default";
private static boolean sVerboseLoggingEnabled = true;
private static boolean sHalVerboseLoggingEnabled = true;
private boolean mInitializationStarted = false;
private static final int RESULT_NOT_VALID = -1;
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 final Object mLock = new Object();
// Supplicant HAL AIDL interface objects
private ISupplicant mISupplicant = null;
private ISupplicantP2pIface mISupplicantP2pIface = null;
private final DeathRecipient mSupplicantDeathRecipient =
() -> {
Log.w(TAG, "ISupplicant/ISupplicantP2pIface died");
synchronized (mLock) {
supplicantServiceDiedHandler();
}
};
private final WifiP2pMonitor mMonitor;
private ISupplicantP2pIfaceCallback mCallback = null;
public SupplicantP2pIfaceHalAidlImpl(WifiP2pMonitor monitor) {
mMonitor = monitor;
}
/**
* Enable verbose logging for all sub modules.
*
* @param verboseEnabled Verbose flag set in overlay XML.
* @param halVerboseEnabled Verbose flag set by the user.
*/
public static void enableVerboseLogging(boolean verboseEnabled, boolean halVerboseEnabled) {
sVerboseLoggingEnabled = verboseEnabled;
sHalVerboseLoggingEnabled = halVerboseEnabled;
SupplicantP2pIfaceCallbackAidlImpl.enableVerboseLogging(verboseEnabled, halVerboseEnabled);
}
/**
* Set the debug log level for wpa_supplicant
*
* @param turnOnVerbose Whether to turn on verbose logging or not.
* @param globalShowKeys Whether show keys is true in WifiGlobals.
* @return true if request is sent successfully, false otherwise.
*/
public boolean setLogLevel(boolean turnOnVerbose, boolean globalShowKeys) {
synchronized (mLock) {
int logLevel = turnOnVerbose
? DebugLevel.DEBUG
: DebugLevel.INFO;
return setDebugParams(logLevel, false,
turnOnVerbose && globalShowKeys);
}
}
/** See ISupplicant.hal for documentation */
private boolean setDebugParams(int level, boolean showTimestamp, boolean showKeys) {
synchronized (mLock) {
String methodStr = "setDebugParams";
if (!checkSupplicantAndLogFailure(methodStr)) {
return false;
}
try {
mISupplicant.setDebugParams(level, showTimestamp, showKeys);
return true;
} catch (RemoteException e) {
handleRemoteException(e, methodStr);
} catch (ServiceSpecificException e) {
handleServiceSpecificException(e, methodStr);
}
return false;
}
}
/**
* Retrieve the ISupplicant service and link to service death.
* @return true if successful, false otherwise
*/
public boolean initialize() {
synchronized (mLock) {
final String methodStr = "initialize";
if (mISupplicant != null) {
Log.i(TAG, "Service is already initialized.");
return true;
}
mInitializationStarted = true;
mISupplicantP2pIface = null;
mISupplicant = getSupplicantMockable();
if (mISupplicant == null) {
Log.e(TAG, "Unable to obtain ISupplicant binder.");
return false;
}
Log.i(TAG, "Obtained ISupplicant binder.");
try {
IBinder serviceBinder = getServiceBinderMockable();
if (serviceBinder == null) {
return false;
}
serviceBinder.linkToDeath(mSupplicantDeathRecipient, /* flags= */ 0);
return true;
} catch (RemoteException e) {
handleRemoteException(e, methodStr);
return false;
}
}
}
/**
* 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) {
// P2P iface already exists
return false;
}
ISupplicantP2pIface iface = addIface(ifaceName);
if (iface == null) {
Log.e(TAG, "Unable to add iface " + ifaceName);
return false;
}
mISupplicantP2pIface = iface;
if (mMonitor != null) {
ISupplicantP2pIfaceCallback callback =
new SupplicantP2pIfaceCallbackAidlImpl(ifaceName, mMonitor);
if (!registerCallback(callback)) {
Log.e(TAG, "Unable to register callback for iface " + ifaceName);
return false;
}
mCallback = callback;
}
return true;
}
}
private ISupplicantP2pIface addIface(@NonNull String ifaceName) {
synchronized (mLock) {
String methodStr = "addIface";
if (!checkSupplicantAndLogFailure(methodStr)) {
return null;
}
try {
return mISupplicant.addP2pInterface(ifaceName);
} catch (RemoteException e) {
handleRemoteException(e, methodStr);
} catch (ServiceSpecificException e) {
handleServiceSpecificException(e, methodStr);
}
return null;
}
}
/**
* Teardown the P2P interface.
*
* @param ifaceName Name of the interface.
* @return true on success, false otherwise.
*/
public boolean teardownIface(@NonNull String ifaceName) {
synchronized (mLock) {
final String methodStr = "teardownIface";
if (!checkSupplicantAndLogFailure(methodStr)) {
return false;
} else if (!checkP2pIfaceAndLogFailure(methodStr)) {
return false;
}
try {
IfaceInfo ifaceInfo = new IfaceInfo();
ifaceInfo.name = ifaceName;
ifaceInfo.type = IfaceType.P2P;
mISupplicant.removeInterface(ifaceInfo);
mISupplicantP2pIface = null;
mCallback = null;
return true;
} catch (RemoteException e) {
handleRemoteException(e, methodStr);
} catch (ServiceSpecificException e) {
handleServiceSpecificException(e, methodStr);
}
return false;
}
}
private void supplicantServiceDiedHandler() {
synchronized (mLock) {
mISupplicant = null;
mISupplicantP2pIface = null;
mInitializationStarted = false;
}
}
/**
* Signals whether initialization started successfully.
*/
public boolean isInitializationStarted() {
synchronized (mLock) {
return mInitializationStarted;
}
}
/**
* Signals whether initialization completed successfully.
*/
public boolean isInitializationComplete() {
synchronized (mLock) {
return mISupplicant != null;
}
}
/**
* Indicates whether the AIDL service is declared
*/
public static boolean serviceDeclared() {
// Service Manager API ServiceManager#isDeclared supported after T.
if (!SdkLevel.isAtLeastT()) {
return false;
}
return ServiceManager.isDeclared(HAL_INSTANCE_NAME);
}
/**
* Wrapper functions to access static HAL methods, created to be mockable in unit tests
*/
@VisibleForTesting
protected ISupplicant getSupplicantMockable() {
synchronized (mLock) {
try {
return ISupplicant.Stub.asInterface(
ServiceManager.waitForDeclaredService(HAL_INSTANCE_NAME));
} catch (Exception e) {
Log.e(TAG, "Unable to get ISupplicant service, " + e);
return null;
}
}
}
@VisibleForTesting
protected IBinder getServiceBinderMockable() {
synchronized (mLock) {
if (mISupplicant == null) {
return null;
}
return mISupplicant.asBinder();
}
}
/**
* Returns false if mISupplicant is null and logs failure message
*/
private boolean checkSupplicantAndLogFailure(String methodStr) {
synchronized (mLock) {
if (mISupplicant == null) {
Log.e(TAG, "Can't call " + methodStr + ", ISupplicant is null");
return false;
}
return true;
}
}
/**
* Returns false if SupplicantP2pIface is null, and logs failure to call methodStr
*/
private boolean checkP2pIfaceAndLogFailure(String methodStr) {
synchronized (mLock) {
if (mISupplicantP2pIface == null) {
Log.e(TAG, "Can't call " + methodStr + ", ISupplicantP2pIface is null");
return false;
}
return true;
}
}
private void handleRemoteException(RemoteException e, String methodStr) {
synchronized (mLock) {
supplicantServiceDiedHandler();
Log.e(TAG,
"ISupplicantP2pIface." + methodStr + " failed with remote exception: ", e);
}
}
private void handleServiceSpecificException(ServiceSpecificException e, String methodStr) {
synchronized (mLock) {
Log.e(TAG, "ISupplicantP2pIface." + methodStr + " failed with "
+ "service specific exception: ", e);
}
}
private int wpsInfoToConfigMethod(int info) {
switch (info) {
case WpsInfo.PBC:
return WpsProvisionMethod.PBC;
case WpsInfo.DISPLAY:
return WpsProvisionMethod.DISPLAY;
case WpsInfo.KEYPAD:
case WpsInfo.LABEL:
return 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) {
String methodStr = "getName";
if (!checkP2pIfaceAndLogFailure(methodStr)) {
return null;
}
try {
return mISupplicantP2pIface.getName();
} catch (RemoteException e) {
handleRemoteException(e, methodStr);
} catch (ServiceSpecificException e) {
handleServiceSpecificException(e, methodStr);
}
return null;
}
}
/**
* 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 callback An instance of the |ISupplicantP2pIfaceCallback| AIDL
* interface object.
* @return boolean value indicating whether operation was successful.
*/
public boolean registerCallback(ISupplicantP2pIfaceCallback callback) {
synchronized (mLock) {
String methodStr = "registerCallback";
if (!checkP2pIfaceAndLogFailure(methodStr)) {
return false;
}
try {
mISupplicantP2pIface.registerCallback(callback);
return true;
} catch (RemoteException e) {
handleRemoteException(e, methodStr);
} catch (ServiceSpecificException e) {
handleServiceSpecificException(e, methodStr);
}
return false;
}
}
/** See {@link ISupplicantStaNetwork#find(int, int)} for documentation. */
public boolean find(int freq, int timeout) {
synchronized (mLock) {
String methodStr = "find";
if (!checkP2pIfaceAndLogFailure(methodStr)) {
return false;
}
if (timeout < 0) {
Log.e(TAG, "Invalid timeout value: " + timeout);
return false;
}
if (freq < 0 && freq != WifiP2pManager.WIFI_P2P_SCAN_SOCIAL) {
Log.e(TAG, "Invalid freq value: " + freq);
return false;
}
try {
switch (freq) {
case WifiP2pManager.WIFI_P2P_SCAN_FULL:
mISupplicantP2pIface.find(timeout);
break;
case WifiP2pManager.WIFI_P2P_SCAN_SOCIAL:
mISupplicantP2pIface.findOnSocialChannels(timeout);
break;
default:
mISupplicantP2pIface.findOnSpecificFrequency(freq, timeout);
break;
}
return true;
} catch (RemoteException e) {
handleRemoteException(e, methodStr);
} catch (ServiceSpecificException e) {
handleServiceSpecificException(e, methodStr);
}
return false;
}
}
/**
* Stop an ongoing P2P service discovery.
*
* @return boolean value indicating whether operation was successful.
*/
public boolean stopFind() {
synchronized (mLock) {
String methodStr = "stopFind";
if (!checkP2pIfaceAndLogFailure(methodStr)) {
return false;
}
try {
mISupplicantP2pIface.stopFind();
return true;
} catch (RemoteException e) {
handleRemoteException(e, methodStr);
} catch (ServiceSpecificException e) {
handleServiceSpecificException(e, methodStr);
}
return false;
}
}
/**
* Flush P2P peer table and state.
*
* @return boolean value indicating whether operation was successful.
*/
public boolean flush() {
synchronized (mLock) {
String methodStr = "flush";
if (!checkP2pIfaceAndLogFailure(methodStr)) {
return false;
}
try {
mISupplicantP2pIface.flush();
return true;
} catch (RemoteException e) {
handleRemoteException(e, methodStr);
} catch (ServiceSpecificException e) {
handleServiceSpecificException(e, methodStr);
}
return false;
}
}
/**
* 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) {
String methodStr = "serviceFlush";
if (!checkP2pIfaceAndLogFailure(methodStr)) {
return false;
}
try {
mISupplicantP2pIface.flushServices();
return true;
} catch (RemoteException e) {
handleRemoteException(e, methodStr);
} catch (ServiceSpecificException e) {
handleServiceSpecificException(e, methodStr);
}
return false;
}
}
/**
* 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) {
String methodStr = "setPowerSave";
if (!checkP2pIfaceAndLogFailure(methodStr)) {
return false;
}
try {
mISupplicantP2pIface.setPowerSave(groupIfName, enable);
return true;
} catch (RemoteException e) {
handleRemoteException(e, methodStr);
} catch (ServiceSpecificException e) {
handleServiceSpecificException(e, methodStr);
}
return false;
}
}
/**
* 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) {
String methodStr = "setGroupIdle";
if (!checkP2pIfaceAndLogFailure(methodStr)) {
return false;
}
// Basic checking here. Leave actual parameter validation to supplicant.
if (timeoutInSec < 0) {
Log.e(TAG, "Invalid group timeout value " + timeoutInSec);
return false;
}
if (groupIfName == null) {
Log.e(TAG, "Group interface name cannot be null.");
return false;
}
try {
mISupplicantP2pIface.setGroupIdle(groupIfName, timeoutInSec);
return true;
} catch (RemoteException e) {
handleRemoteException(e, methodStr);
} catch (ServiceSpecificException e) {
handleServiceSpecificException(e, methodStr);
}
return false;
}
}
/**
* 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) {
String methodStr = "setSsidPostfix";
if (!checkP2pIfaceAndLogFailure(methodStr)) {
return false;
}
// Basic checking here. Leave actual parameter validation to supplicant.
if (postfix == null) {
Log.e(TAG, "Invalid SSID postfix value (null).");
return false;
}
try {
mISupplicantP2pIface.setSsidPostfix(
NativeUtil.byteArrayFromArrayList(
NativeUtil.decodeSsid("\"" + postfix + "\"")));
return true;
} catch (RemoteException e) {
handleRemoteException(e, methodStr);
} catch (ServiceSpecificException e) {
handleServiceSpecificException(e, methodStr);
} catch (IllegalArgumentException e) {
Log.e(TAG, "Could not decode SSID.", e);
}
return false;
}
}
/**
* 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) {
synchronized (mLock) {
String methodStr = "setSsidPostfix";
if (!checkP2pIfaceAndLogFailure(methodStr)) {
return null;
}
if (config == null) {
Log.e(TAG, "Could not connect because config is null.");
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 (IllegalArgumentException 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.NETWORK_ID_PERSISTENT);
if (config.groupOwnerIntent < 0 || config.groupOwnerIntent > 15) {
Log.e(TAG, "Invalid group owner intent: " + config.groupOwnerIntent);
return null;
}
try {
return mISupplicantP2pIface.connect(
peerAddress, provisionMethod, preSelectedPin, joinExistingGroup,
persistent, config.groupOwnerIntent);
} catch (RemoteException e) {
handleRemoteException(e, methodStr);
} catch (ServiceSpecificException e) {
handleServiceSpecificException(e, methodStr);
}
return null;
}
}
/**
* 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) {
String methodStr = "cancelConnect";
if (!checkP2pIfaceAndLogFailure(methodStr)) {
return false;
}
try {
mISupplicantP2pIface.cancelConnect();
return true;
} catch (RemoteException e) {
handleRemoteException(e, methodStr);
} catch (ServiceSpecificException e) {
handleServiceSpecificException(e, methodStr);
}
return false;
}
}
/**
* 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) {
synchronized (mLock) {
String methodStr = "provisionDiscovery";
if (!checkP2pIfaceAndLogFailure("provisionDiscovery")) {
return false;
}
if (config == null) {
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 == WpsProvisionMethod.DISPLAY) {
// We are doing display, so provision discovery is keypad.
targetMethod = WpsProvisionMethod.KEYPAD;
} else if (targetMethod == WpsProvisionMethod.KEYPAD) {
// We are doing keypad, so provision discovery is display.
targetMethod = 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 (IllegalArgumentException e) {
Log.e(TAG, "Could not parse peer mac address.", e);
return false;
}
try {
mISupplicantP2pIface.provisionDiscovery(macAddress, targetMethod);
return true;
} catch (RemoteException e) {
handleRemoteException(e, methodStr);
} catch (ServiceSpecificException e) {
handleServiceSpecificException(e, methodStr);
}
return false;
}
}
/**
* 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) {
synchronized (mLock) {
String methodStr = "invite";
if (!checkP2pIfaceAndLogFailure(methodStr)) {
return false;
}
if (TextUtils.isEmpty(peerAddress)) {
Log.e(TAG, "Peer mac address is empty.");
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 (IllegalArgumentException e) {
Log.e(TAG, "Group owner mac address parse error.", e);
return false;
}
byte[] peerMacAddress;
try {
peerMacAddress = NativeUtil.macAddressToByteArray(peerAddress);
} catch (IllegalArgumentException e) {
Log.e(TAG, "Peer mac address parse error.", e);
return false;
}
try {
mISupplicantP2pIface.invite(
group.getInterface(), ownerMacAddress, peerMacAddress);
return true;
} catch (RemoteException e) {
handleRemoteException(e, methodStr);
} catch (ServiceSpecificException e) {
handleServiceSpecificException(e, methodStr);
}
return false;
}
}
/**
* 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) {
String methodStr = "reject";
if (!checkP2pIfaceAndLogFailure(methodStr)) {
return false;
}
if (peerAddress == null) {
Log.e(TAG, "Rejected peer's mac address is null.");
return false;
}
byte[] macAddress = null;
try {
macAddress = NativeUtil.macAddressToByteArray(peerAddress);
} catch (IllegalArgumentException e) {
Log.e(TAG, "Could not parse peer mac address.", e);
return false;
}
try {
mISupplicantP2pIface.reject(macAddress);
return true;
} catch (RemoteException e) {
handleRemoteException(e, methodStr);
} catch (ServiceSpecificException e) {
handleServiceSpecificException(e, methodStr);
}
return false;
}
}
/**
* Gets the MAC address of the device.
*
* @return MAC address of the device.
*/
public String getDeviceAddress() {
synchronized (mLock) {
String methodStr = "getDeviceAddress";
if (!checkP2pIfaceAndLogFailure(methodStr)) {
return null;
}
try {
byte[] address = mISupplicantP2pIface.getDeviceAddress();
return NativeUtil.macAddressFromByteArray(address);
} catch (RemoteException e) {
handleRemoteException(e, methodStr);
} catch (ServiceSpecificException e) {
handleServiceSpecificException(e, methodStr);
} catch (IllegalArgumentException e) {
Log.e(TAG, "Received invalid MAC address", e);
}
return null;
}
}
/**
* 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) {
String methodStr = "getSsid";
if (!checkP2pIfaceAndLogFailure(methodStr)) {
return null;
}
if (address == null) {
Log.e(TAG, "Cannot parse null peer mac address.");
return null;
}
byte[] macAddress = null;
try {
macAddress = NativeUtil.macAddressToByteArray(address);
} catch (IllegalArgumentException e) {
Log.e(TAG, "Could not parse mac address.", e);
return null;
}
try {
byte[] ssid = mISupplicantP2pIface.getSsid(macAddress);
if (ssid == null) {
return null;
}
return NativeUtil.removeEnclosingQuotes(
NativeUtil.encodeSsid(
NativeUtil.byteArrayToArrayList(ssid)));
} catch (RemoteException e) {
handleRemoteException(e, methodStr);
} catch (ServiceSpecificException e) {
handleServiceSpecificException(e, methodStr);
} catch (IllegalArgumentException e) {
Log.e(TAG, "Unable to parse SSID: ", e);
}
return null;
}
}
/**
* 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) {
synchronized (mLock) {
String methodStr = "reinvoke";
if (!checkP2pIfaceAndLogFailure(methodStr)) {
return false;
}
if (TextUtils.isEmpty(peerAddress) || networkId < 0) {
return false;
}
byte[] macAddress = null;
try {
macAddress = NativeUtil.macAddressToByteArray(peerAddress);
} catch (IllegalArgumentException e) {
Log.e(TAG, "Could not parse mac address.", e);
return false;
}
try {
mISupplicantP2pIface.reinvoke(networkId, macAddress);
return true;
} catch (RemoteException e) {
handleRemoteException(e, methodStr);
} catch (ServiceSpecificException e) {
handleServiceSpecificException(e, methodStr);
}
return false;
}
}
/**
* 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) {
String methodStr = "groupAdd";
if (!checkP2pIfaceAndLogFailure(methodStr)) {
return false;
}
try {
mISupplicantP2pIface.addGroup(isPersistent, networkId);
return true;
} catch (RemoteException e) {
handleRemoteException(e, methodStr);
} catch (ServiceSpecificException e) {
handleServiceSpecificException(e, methodStr);
}
return false;
}
}
/**
* 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 preferred frequency 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) {
String methodStr = "groupAdd";
if (!checkP2pIfaceAndLogFailure(methodStr)) {
return false;
}
byte[] macAddress = null;
try {
macAddress = NativeUtil.macAddressToByteArray(peerAddress);
} catch (IllegalArgumentException e) {
Log.e(TAG, "Could not parse mac address.", e);
return false;
}
byte[] ssid = null;
try {
ssid = NativeUtil.byteArrayFromArrayList(
NativeUtil.decodeSsid("\"" + networkName + "\""));
} catch (Exception e) {
Log.e(TAG, "Could not parse ssid.", e);
return false;
}
try {
mISupplicantP2pIface.addGroupWithConfig(
ssid, passphrase, isPersistent, freq, macAddress, join);
return true;
} catch (RemoteException e) {
handleRemoteException(e, methodStr);
} catch (ServiceSpecificException e) {
handleServiceSpecificException(e, methodStr);
}
return false;
}
}
/**
* 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) {
synchronized (mLock) {
String methodStr = "groupRemove";
if (!checkP2pIfaceAndLogFailure(methodStr)) {
return false;
}
if (TextUtils.isEmpty(groupName)) {
return false;
}
try {
mISupplicantP2pIface.removeGroup(groupName);
return true;
} catch (RemoteException e) {
handleRemoteException(e, methodStr);
} catch (ServiceSpecificException e) {
handleServiceSpecificException(e, methodStr);
}
return false;
}
}
/**
* 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) {
String methodStr = "getGroupCapability";
if (!checkP2pIfaceAndLogFailure(methodStr)) {
return RESULT_NOT_VALID;
}
if (peerAddress == null) {
Log.e(TAG, "Cannot parse null peer mac address.");
return RESULT_NOT_VALID;
}
byte[] macAddress = null;
try {
macAddress = NativeUtil.macAddressToByteArray(peerAddress);
} catch (IllegalArgumentException e) {
Log.e(TAG, "Could not parse mac address.", e);
return RESULT_NOT_VALID;
}
try {
return mISupplicantP2pIface.getGroupCapability(macAddress);
} catch (RemoteException e) {
handleRemoteException(e, methodStr);
} catch (ServiceSpecificException e) {
handleServiceSpecificException(e, methodStr);
}
return RESULT_NOT_VALID;
}
}
/**
* 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) {
synchronized (mLock) {
String methodStr = "configureExtListen";
if (!checkP2pIfaceAndLogFailure(methodStr)) {
return false;
}
if (enable && intervalInMillis < periodInMillis) {
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;
}
try {
mISupplicantP2pIface.configureExtListen(periodInMillis, intervalInMillis);
return true;
} catch (RemoteException e) {
handleRemoteException(e, methodStr);
} catch (ServiceSpecificException e) {
handleServiceSpecificException(e, methodStr);
}
return false;
}
}
/**
* Set P2P Listen channel.
*
* @param listenChannel Wifi channel. eg, 1, 6, 11.
*
* @return true, if operation was successful.
*/
public boolean setListenChannel(int listenChannel) {
synchronized (mLock) {
String methodStr = "setListenChannel";
if (!checkP2pIfaceAndLogFailure(methodStr)) {
return false;
}
// There is no original channel recorded in supplicant, so just return true.
if (listenChannel == 0) {
return true;
}
// Using channels other than 1, 6, and 11 would result in a discovery issue.
if (listenChannel != 1 && listenChannel != 6 && listenChannel != 11) {
return false;
}
try {
mISupplicantP2pIface.setListenChannel(listenChannel, DEFAULT_OPERATING_CLASS);
return true;
} catch (RemoteException e) {
handleRemoteException(e, methodStr);
} catch (ServiceSpecificException e) {
handleServiceSpecificException(e, methodStr);
}
return false;
}
}
/**
* Set P2P operating channel.
*
* @param operatingChannel the desired operating channel.
* @param unsafeChannels channels which p2p cannot use.
*
* @return true, if operation was successful.
*/
public boolean setOperatingChannel(int operatingChannel,
@NonNull List<CoexUnsafeChannel> unsafeChannels) {
synchronized (mLock) {
String methodStr = "setOperatingChannel";
if (!checkP2pIfaceAndLogFailure(methodStr)) {
return false;
}
if (unsafeChannels == null) {
return false;
}
ArrayList<FreqRange> ranges = new ArrayList<>();
if (operatingChannel >= 1 && operatingChannel <= 165) {
int freq = (operatingChannel <= 14 ? 2407 : 5000) + operatingChannel * 5;
FreqRange range1 = new FreqRange();
range1.min = 1000;
range1.max = freq - 5;
FreqRange range2 = new FreqRange();
range2.min = freq + 5;
range2.max = 6000;
ranges.add(range1);
ranges.add(range2);
}
if (SdkLevel.isAtLeastS()) {
for (CoexUnsafeChannel cuc: unsafeChannels) {
int centerFreq = ScanResult.convertChannelToFrequencyMhzIfSupported(
cuc.getChannel(), cuc.getBand());
FreqRange range = new FreqRange();
// The range boundaries are inclusive in native frequency inclusion check.
// Subtract one to avoid affecting neighbors.
range.min = centerFreq - 5 - 1;
range.max = centerFreq + 5 - 1;
ranges.add(range);
}
}
FreqRange[] rangeArr = new FreqRange[ranges.size()];
for (int i = 0; i < ranges.size(); i++) {
rangeArr[i] = ranges.get(i);
}
try {
mISupplicantP2pIface.setDisallowedFrequencies(rangeArr);
return true;
} catch (RemoteException e) {
handleRemoteException(e, methodStr);
} catch (ServiceSpecificException e) {
handleServiceSpecificException(e, methodStr);
}
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) {
String methodStr = "serviceAdd";
if (!checkP2pIfaceAndLogFailure(methodStr)) {
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;
}
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;
}
mISupplicantP2pIface.addUpnpService(version, data[2]);
} else if ("bonjour".equals(data[0])) {
if (data[1] != null && data[2] != null) {
byte[] request = null;
byte[] response = null;
try {
request = NativeUtil.hexStringToByteArray(data[1]);
response = NativeUtil.hexStringToByteArray(data[2]);
} catch (IllegalArgumentException e) {
Log.e(TAG, "Invalid bonjour service description.");
return false;
}
mISupplicantP2pIface.addBonjourService(request, response);
}
} else {
Log.e(TAG, "Unknown / unsupported P2P service requested: " + data[0]);
return false;
}
} catch (RemoteException e) {
handleRemoteException(e, methodStr);
return false;
} catch (ServiceSpecificException e) {
handleServiceSpecificException(e, methodStr);
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) {
String methodStr = "serviceRemove";
if (!checkP2pIfaceAndLogFailure(methodStr)) {
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;
}
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;
}
mISupplicantP2pIface.removeUpnpService(version, data[2]);
} else if ("bonjour".equals(data[0])) {
if (data[1] != null) {
byte[] request = null;
try {
request = NativeUtil.hexStringToByteArray(data[1]);
} catch (IllegalArgumentException e) {
Log.e(TAG, "Invalid bonjour service description.");
return false;
}
mISupplicantP2pIface.removeBonjourService(request);
}
} else {
Log.e(TAG, "Unknown / unsupported P2P service requested: " + data[0]);
return false;
}
} catch (RemoteException e) {
handleRemoteException(e, methodStr);
return false;
} catch (ServiceSpecificException e) {
handleServiceSpecificException(e, methodStr);
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) {
String methodStr = "requestServiceDiscovery";
if (!checkP2pIfaceAndLogFailure(methodStr)) {
return null;
}
if (peerAddress == null) {
Log.e(TAG, "Cannot parse null peer mac address.");
return null;
}
byte[] macAddress = null;
try {
macAddress = NativeUtil.macAddressToByteArray(peerAddress);
} catch (IllegalArgumentException e) {
Log.e(TAG, "Could not process peer MAC address.", e);
return null;
}
if (query == null) {
Log.e(TAG, "Cannot parse null service discovery query.");
return null;
}
byte[] binQuery = null;
try {
binQuery = NativeUtil.hexStringToByteArray(query);
} catch (Exception e) {
Log.e(TAG, "Could not parse service query.", e);
return null;
}
try {
long result = mISupplicantP2pIface.requestServiceDiscovery(macAddress, binQuery);
return Long.toString(result);
} catch (RemoteException e) {
handleRemoteException(e, methodStr);
} catch (ServiceSpecificException e) {
handleServiceSpecificException(e, methodStr);
}
return null;
}
}
/**
* 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) {
String methodStr = "cancelServiceDiscovery";
if (!checkP2pIfaceAndLogFailure(methodStr)) {
return false;
}
if (identifier == null) {
Log.e(TAG, "Received a null service discovery identifier.");
return false;
}
long id = 0;
try {
id = Long.parseLong(identifier);
} catch (NumberFormatException e) {
Log.e(TAG, "Service discovery identifier invalid: " + identifier, e);
return false;
}
try {
mISupplicantP2pIface.cancelServiceDiscovery(id);
return true;
} catch (RemoteException e) {
handleRemoteException(e, methodStr);
} catch (ServiceSpecificException e) {
handleServiceSpecificException(e, methodStr);
}
return false;
}
}
/**
* 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) {
String methodStr = "setMiracastMode";
if (!checkP2pIfaceAndLogFailure(methodStr)) {
return false;
}
byte targetMode = MiracastMode.DISABLED;
switch (mode) {
case WifiP2pManager.MIRACAST_SOURCE:
targetMode = MiracastMode.SOURCE;
break;
case WifiP2pManager.MIRACAST_SINK:
targetMode = MiracastMode.SINK;
break;
}
try {
mISupplicantP2pIface.setMiracastMode(targetMode);
return true;
} catch (RemoteException e) {
handleRemoteException(e, methodStr);
} catch (ServiceSpecificException e) {
handleServiceSpecificException(e, methodStr);
}
return false;
}
}
/**
* 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) {
synchronized (mLock) {
String methodStr = "startWpsPbc";
if (!checkP2pIfaceAndLogFailure(methodStr)) {
return false;
}
if (TextUtils.isEmpty(groupIfName)) {
Log.e(TAG, "Group name required when requesting WPS PBC. Got empty string.");
return false;
}
// Null values should be fine, since bssid can be empty.
byte[] macAddress = null;
try {
macAddress = NativeUtil.macAddressToByteArray(bssid);
} catch (IllegalArgumentException e) {
Log.e(TAG, "Could not parse BSSID.", e);
return false;
}
try {
mISupplicantP2pIface.startWpsPbc(groupIfName, macAddress);
return true;
} catch (RemoteException e) {
handleRemoteException(e, methodStr);
} catch (ServiceSpecificException e) {
handleServiceSpecificException(e, methodStr);
}
return false;
}
}
/**
* 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) {
synchronized (mLock) {
String methodStr = "startWpsPinKeypad";
if (!checkP2pIfaceAndLogFailure(methodStr)) {
return false;
}
if (TextUtils.isEmpty(groupIfName)) {
Log.e(TAG, "Group name required when requesting WPS KEYPAD.");
return false;
}
if (TextUtils.isEmpty(pin)) {
Log.e(TAG, "PIN required when requesting WPS KEYPAD.");
return false;
}
try {
mISupplicantP2pIface.startWpsPinKeypad(groupIfName, pin);
return true;
} catch (RemoteException e) {
handleRemoteException(e, methodStr);
} catch (ServiceSpecificException e) {
handleServiceSpecificException(e, methodStr);
}
return false;
}
}
/**
* 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) {
synchronized (mLock) {
String methodStr = "startWpsPinDisplay";
if (!checkP2pIfaceAndLogFailure(methodStr)) {
return null;
}
if (TextUtils.isEmpty(groupIfName)) {
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 (IllegalArgumentException e) {
Log.e(TAG, "Could not parse BSSID.", e);
return null;
}
try {
return mISupplicantP2pIface.startWpsPinDisplay(groupIfName, macAddress);
} catch (RemoteException e) {
handleRemoteException(e, methodStr);
} catch (ServiceSpecificException e) {
handleServiceSpecificException(e, methodStr);
}
return null;
}
}
/**
* 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) {
String methodStr = "cancelWps";
if (!checkP2pIfaceAndLogFailure(methodStr)) {
return false;
}
if (groupIfName == null) {
Log.e(TAG, "Group name required when requesting WPS KEYPAD.");
return false;
}
try {
mISupplicantP2pIface.cancelWps(groupIfName);
return true;
} catch (RemoteException e) {
handleRemoteException(e, methodStr);
} catch (ServiceSpecificException e) {
handleServiceSpecificException(e, methodStr);
}
return false;
}
}
/**
* 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) {
String methodStr = "enableWfd";
if (!checkP2pIfaceAndLogFailure(methodStr)) {
return false;
}
try {
mISupplicantP2pIface.enableWfd(enable);
return true;
} catch (RemoteException e) {
handleRemoteException(e, methodStr);
} catch (ServiceSpecificException e) {
handleServiceSpecificException(e, methodStr);
}
return false;
}
}
/**
* 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) {
String methodStr = "setWfdDeviceInfo";
if (!checkP2pIfaceAndLogFailure(methodStr)) {
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 (IllegalArgumentException e) {
Log.e(TAG, "Could not parse WFD Device Info string.");
return false;
}
try {
mISupplicantP2pIface.setWfdDeviceInfo(wfdInfo);
return true;
} catch (RemoteException e) {
handleRemoteException(e, methodStr);
} catch (ServiceSpecificException e) {
handleServiceSpecificException(e, methodStr);
}
return false;
}
}
/**
* 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) {
String methodStr = "removeNetwork";
if (!checkP2pIfaceAndLogFailure(methodStr)) {
return false;
}
try {
mISupplicantP2pIface.removeNetwork(networkId);
return true;
} catch (RemoteException e) {
handleRemoteException(e, methodStr);
} catch (ServiceSpecificException e) {
handleServiceSpecificException(e, methodStr);
}
return false;
}
}
/**
* List the networks saved in wpa_supplicant.
*
* @return List of network ids.
*/
private int[] listNetworks() {
synchronized (mLock) {
String methodStr = "listNetworks";
if (!checkP2pIfaceAndLogFailure(methodStr)) {
return null;
}
try {
return mISupplicantP2pIface.listNetworks();
} catch (RemoteException e) {
handleRemoteException(e, methodStr);
} catch (ServiceSpecificException e) {
handleServiceSpecificException(e, methodStr);
}
return null;
}
}
/**
* 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) {
String methodStr = "getNetwork";
if (!checkP2pIfaceAndLogFailure(methodStr)) {
return null;
}
try {
return mISupplicantP2pIface.getNetwork(networkId);
} catch (RemoteException e) {
handleRemoteException(e, methodStr);
} catch (ServiceSpecificException e) {
handleServiceSpecificException(e, methodStr);
}
return null;
}
}
/**
* 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) {
String methodStr = "loadGroups";
if (!checkP2pIfaceAndLogFailure(methodStr)) {
return false;
}
int[] networkIds = listNetworks();
if (networkIds == null || networkIds.length == 0) {
return false;
}
for (int networkId : networkIds) {
ISupplicantP2pNetwork network = getNetwork(networkId);
if (network == null) {
Log.e(TAG, "Failed to retrieve network object for " + networkId);
continue;
}
boolean gotResult = false;
boolean isCurrent = false;
try {
isCurrent = network.isCurrent();
gotResult = true;
} catch (RemoteException e) {
handleRemoteException(e, methodStr);
} catch (ServiceSpecificException e) {
handleServiceSpecificException(e, methodStr);
}
/** Skip the current network, if we're somehow getting networks from the p2p GO
interface, instead of p2p mgmt interface*/
if (!gotResult || isCurrent) {
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.
byte[] ssid = null;
gotResult = false;
try {
ssid = network.getSsid();
gotResult = true;
} catch (RemoteException e) {
handleRemoteException(e, methodStr);
} catch (ServiceSpecificException e) {
handleServiceSpecificException(e, methodStr);
}
if (gotResult && !ArrayUtils.isEmpty(ssid)) {
group.setNetworkName(NativeUtil.removeEnclosingQuotes(
NativeUtil.encodeSsid(
NativeUtil.byteArrayToArrayList(ssid))));
}
byte[] bssid = null;
gotResult = false;
try {
bssid = network.getBssid();
gotResult = true;
} catch (RemoteException e) {
handleRemoteException(e, methodStr);
} catch (ServiceSpecificException e) {
handleServiceSpecificException(e, methodStr);
}
if (gotResult && !ArrayUtils.isEmpty(bssid)) {
WifiP2pDevice device = new WifiP2pDevice();
device.deviceAddress = NativeUtil.macAddressFromByteArray(bssid);
group.setOwner(device);
}
boolean isGroupOwner = false;
gotResult = false;
try {
isGroupOwner = network.isGroupOwner();
gotResult = true;
} catch (RemoteException e) {
handleRemoteException(e, methodStr);
} catch (ServiceSpecificException e) {
handleServiceSpecificException(e, methodStr);
}
if (gotResult) {
group.setIsGroupOwner(isGroupOwner);
}
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) {
synchronized (mLock) {
String methodStr = "setWpsDeviceName";
if (!checkP2pIfaceAndLogFailure(methodStr)) {
return false;
}
if (name == null) {
return false;
}
try {
mISupplicantP2pIface.setWpsDeviceName(name);
return true;
} catch (RemoteException e) {
handleRemoteException(e, methodStr);
} catch (ServiceSpecificException e) {
handleServiceSpecificException(e, methodStr);
}
return false;
}
}
/**
* 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) {
String methodStr = "setWpsDeviceType";
if (!checkP2pIfaceAndLogFailure(methodStr)) {
return false;
}
try {
mISupplicantP2pIface.setWpsDeviceType(bytes);
return true;
} catch (RemoteException e) {
handleRemoteException(e, methodStr);
} catch (ServiceSpecificException e) {
handleServiceSpecificException(e, methodStr);
}
}
} 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) {
String methodStr = "setWpsConfigMethods";
if (!checkP2pIfaceAndLogFailure(methodStr)) {
return false;
}
short configMethodsMask = 0;
String[] configMethodsStrArr = configMethodsStr.split("\\s+");
for (int i = 0; i < configMethodsStrArr.length; i++) {
configMethodsMask |= stringToWpsConfigMethod(configMethodsStrArr[i]);
}
try {
mISupplicantP2pIface.setWpsConfigMethods(configMethodsMask);
return true;
} catch (RemoteException e) {
handleRemoteException(e, methodStr);
} catch (ServiceSpecificException e) {
handleServiceSpecificException(e, methodStr);
}
return false;
}
}
/**
* Get NFC handover request message.
*
* @return select message if created successfully, null otherwise.
*/
public String getNfcHandoverRequest() {
synchronized (mLock) {
String methodStr = "getNfcHandoverRequest";
if (!checkP2pIfaceAndLogFailure(methodStr)) {
return null;
}
try {
byte[] message = mISupplicantP2pIface.createNfcHandoverRequestMessage();
return NativeUtil.hexStringFromByteArray(message);
} catch (RemoteException e) {
handleRemoteException(e, methodStr);
} catch (ServiceSpecificException e) {
handleServiceSpecificException(e, methodStr);
} catch (IllegalArgumentException e) {
Log.e(TAG, "Invalid message received ", e);
}
return null;
}
}
/**
* Get NFC handover select message.
*
* @return select message if created successfully, null otherwise.
*/
public String getNfcHandoverSelect() {
synchronized (mLock) {
String methodStr = "getNfcHandoverSelect";
if (!checkP2pIfaceAndLogFailure(methodStr)) {
return null;
}
try {
byte[] message = mISupplicantP2pIface.createNfcHandoverSelectMessage();
return NativeUtil.hexStringFromByteArray(message);
} catch (RemoteException e) {
handleRemoteException(e, methodStr);
} catch (ServiceSpecificException e) {
handleServiceSpecificException(e, methodStr);
} catch (IllegalArgumentException e) {
Log.e(TAG, "Invalid message received ", e);
}
return null;
}
}
/**
* Report NFC handover select message.
*
* @return true if reported successfully, false otherwise.
*/
public boolean initiatorReportNfcHandover(String selectMessage) {
synchronized (mLock) {
String methodStr = "initiatorReportNfcHandover";
if (!checkP2pIfaceAndLogFailure(methodStr)) {
return false;
}
if (selectMessage == null) {
return false;
}
try {
mISupplicantP2pIface.reportNfcHandoverInitiation(
NativeUtil.hexStringToByteArray(selectMessage));
return true;
} catch (RemoteException e) {
handleRemoteException(e, methodStr);
} catch (ServiceSpecificException e) {
handleServiceSpecificException(e, methodStr);
} catch (IllegalArgumentException e) {
Log.e(TAG, "Illegal argument " + selectMessage, e);
}
return false;
}
}
/**
* Report NFC handover request message.
*
* @return true if reported successfully, false otherwise.
*/
public boolean responderReportNfcHandover(String requestMessage) {
synchronized (mLock) {
String methodStr = "responderReportNfcHandover";
if (!checkP2pIfaceAndLogFailure(methodStr)) {
return false;
}
if (requestMessage == null) {
return false;
}
try {
mISupplicantP2pIface.reportNfcHandoverResponse(
NativeUtil.hexStringToByteArray(requestMessage));
return true;
} catch (RemoteException e) {
handleRemoteException(e, methodStr);
} catch (ServiceSpecificException e) {
handleServiceSpecificException(e, methodStr);
} catch (IllegalArgumentException e) {
Log.e(TAG, "Illegal argument " + requestMessage, e);
}
return false;
}
}
/**
* 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) {
String methodStr = "setClientList";
if (!checkP2pIfaceAndLogFailure(methodStr)) {
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;
}
try {
String[] clientListArr = clientListStr.split("\\s+");
android.hardware.wifi.supplicant.MacAddress[] clients =
new android.hardware.wifi.supplicant.MacAddress[clientListArr.length];
for (int i = 0; i < clientListArr.length; i++) {
android.hardware.wifi.supplicant.MacAddress client =
new android.hardware.wifi.supplicant.MacAddress();
client.data = NativeUtil.macAddressToByteArray(clientListArr[i]);
clients[i] = client;
}
network.setClientList(clients);
return true;
} catch (RemoteException e) {
handleRemoteException(e, methodStr);
} catch (ServiceSpecificException e) {
handleServiceSpecificException(e, methodStr);
} catch (IllegalArgumentException e) {
Log.e(TAG, "Illegal argument " + clientListStr, e);
}
return false;
}
}
/**
* Set the client list for the provided network.
*
* @param networkId Id of the network.
* @return Space separated list of clients if successful, null otherwise.
*/
public String getClientList(int networkId) {
synchronized (mLock) {
String methodStr = "getClientList";
if (!checkP2pIfaceAndLogFailure(methodStr)) {
return null;
}
ISupplicantP2pNetwork network = getNetwork(networkId);
if (network == null) {
Log.e(TAG, "Invalid network id ");
return null;
}
try {
android.hardware.wifi.supplicant.MacAddress[] clients = network.getClientList();
String[] macStrings = new String[clients.length];
for (int i = 0; i < clients.length; i++) {
try {
macStrings[i] = NativeUtil.macAddressFromByteArray(clients[i].data);
} catch (Exception e) {
Log.e(TAG, "Invalid MAC address received ", e);
return null;
}
}
return String.join(" ", macStrings);
} catch (RemoteException e) {
handleRemoteException(e, methodStr);
} catch (ServiceSpecificException e) {
handleServiceSpecificException(e, methodStr);
}
return null;
}
}
/**
* Persist the current configurations to disk.
*
* @return true, if operation was successful.
*/
public boolean saveConfig() {
synchronized (mLock) {
String methodStr = "saveConfig";
if (!checkP2pIfaceAndLogFailure(methodStr)) {
return false;
}
try {
mISupplicantP2pIface.saveConfig();
return true;
} catch (RemoteException e) {
handleRemoteException(e, methodStr);
} catch (ServiceSpecificException e) {
handleServiceSpecificException(e, methodStr);
}
return false;
}
}
/**
* 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) {
String methodStr = "setMacRandomization";
if (!checkP2pIfaceAndLogFailure(methodStr)) {
return false;
}
try {
mISupplicantP2pIface.setMacRandomization(enable);
return true;
} catch (RemoteException e) {
handleRemoteException(e, methodStr);
} catch (ServiceSpecificException e) {
handleServiceSpecificException(e, methodStr);
}
return false;
}
}
/**
* Set Wifi Display R2 device info.
*
* @param info WFD R2 device info as described in section 5.1.12 of WFD technical
* specification v2.1.
* @return true, if operation was successful.
*/
public boolean setWfdR2DeviceInfo(String info) {
synchronized (mLock) {
String methodStr = "setWfdR2DeviceInfo";
if (info == null) {
Log.e(TAG, "Cannot parse null WFD info string.");
return false;
}
byte[] wfdR2Info = null;
try {
wfdR2Info = NativeUtil.hexStringToByteArray(info);
} catch (IllegalArgumentException e) {
Log.e(TAG, "Could not parse WFD R2 Device Info string.");
return false;
}
if (!checkP2pIfaceAndLogFailure(methodStr)) {
return false;
}
try {
mISupplicantP2pIface.setWfdR2DeviceInfo(wfdR2Info);
return true;
} catch (RemoteException e) {
handleRemoteException(e, methodStr);
} catch (ServiceSpecificException e) {
handleServiceSpecificException(e, methodStr);
}
return false;
}
}
/**
* Remove the client with the MAC address from the group.
*
* @param peerAddress Mac address of the client.
* @param isLegacyClient Indicate if client is a legacy client or not.
* @return true if success
*/
public boolean removeClient(String peerAddress, boolean isLegacyClient) {
synchronized (mLock) {
String methodStr = "removeClient";
if (peerAddress == null) {
Log.e(TAG, "Cannot parse null peer mac address.");
return false;
}
byte[] peerMacAddress;
try {
peerMacAddress = NativeUtil.macAddressToByteArray(peerAddress);
} catch (IllegalArgumentException e) {
Log.e(TAG, "Peer mac address parse error.", e);
return false;
}
if (!checkP2pIfaceAndLogFailure(methodStr)) {
return false;
}
try {
mISupplicantP2pIface.removeClient(peerMacAddress, isLegacyClient);
return true;
} catch (RemoteException e) {
handleRemoteException(e, methodStr);
} catch (ServiceSpecificException e) {
handleServiceSpecificException(e, methodStr);
}
return false;
}
}
/**
* 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);
}
}
}