/* * 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.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.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 boolean DBG = true; private static final String TAG = "SupplicantP2pIfaceHal"; 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: "--"; */ 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 (DBG) { Log.i(TAG, "IServiceNotification.onRegistration for: " + fqName + ", " + name + " preexisting=" + preexisting); } if (!initSupplicantService() || !initSupplicantP2pIface()) { Log.e(TAG, "initalizing ISupplicantIfaces 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; } /** * 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 (DBG) 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; } private boolean initSupplicantP2pIface() { synchronized (mLock) { /** List all supplicant Ifaces */ final ArrayList supplicantIfaces = new ArrayList(); try { mISupplicant.listInterfaces((SupplicantStatus status, ArrayList 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 false; } if (supplicantIfaces.size() == 0) { Log.e(TAG, "Got zero HIDL supplicant ifaces. Stopping supplicant HIDL startup."); return false; } SupplicantResult supplicantIface = new SupplicantResult("getInterface()"); for (ISupplicant.IfaceInfo ifaceInfo : supplicantIfaces) { if (ifaceInfo.type == IfaceType.P2P) { 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); return false; } break; } } if (supplicantIface.getResult() == null) { Log.e(TAG, "initSupplicantP2pIface got null iface"); return false; } mISupplicantP2pIface = getP2pIfaceMockable(supplicantIface.getResult()); if (!linkToSupplicantP2pIfaceDeath()) { return false; } } if (mISupplicantP2pIface != null && mMonitor != null) { // TODO(ender): Get rid of hard-coded interface name, which is // assumed to be the group interface name in several other classes // ("p2p0" should probably become getName()). mCallback = new SupplicantP2pIfaceCallback("p2p0", mMonitor); if (!registerCallback(mCallback)) { Log.e(TAG, "Callback registration failed. Initialization incomplete."); return false; } } return true; } private void supplicantServiceDiedHandler() { synchronized (mLock) { mISupplicant = null; mISupplicantP2pIface = null; } } /** * Signals whether Initialization completed successfully. */ public boolean isInitializationStarted() { return mIServiceManager != null; } /** * Signals whether Initialization completed successfully. Only necessary for testing, is not * needed to guard calls etc. */ public boolean isInitializationComplete() { return mISupplicantP2pIface != 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 { return ISupplicant.getService(); } protected ISupplicantP2pIface getP2pIfaceMockable(ISupplicantIface iface) { return ISupplicantP2pIface.asInterface(iface.asBinder()); } protected ISupplicantP2pNetwork getP2pNetworkMockable(ISupplicantNetwork network) { return ISupplicantP2pNetwork.asInterface(network.asBinder()); } protected static void logd(String s) { if (DBG) 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; } 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 result = new SupplicantResult("getSsid(" + address + ")"); try { mISupplicantP2pIface.getSsid( macAddress, (SupplicantStatus status, ArrayList ssid) -> { String ssidString = null; if (ssid != null) { try { ssidString = 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 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 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 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 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 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 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 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 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 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 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 request = null; ArrayList 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 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 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 binQuery = null; try { binQuery = NativeUtil.byteArrayToArrayList(NativeUtil.hexStringToByteArray(query)); } catch (Exception e) { Log.e(TAG, "Could not parse service query.", e); return null; } SupplicantResult 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 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 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 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 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 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 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 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 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 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 listNetworks() { synchronized (mLock) { if (!checkSupplicantP2pIfaceAndLogFailure("listNetworks")) return null; SupplicantResult result = new SupplicantResult("listNetworks()"); try { mISupplicantP2pIface.listNetworks( (SupplicantStatus status, ArrayList 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 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 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 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 resultSsid = new SupplicantResult("getSsid(" + networkId + ")"); try { network.getSsid( (SupplicantStatus status, ArrayList 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 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 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 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: -- * @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 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 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 result = new SupplicantResult( "getNfcHandoverRequest()"); try { mISupplicantP2pIface.createNfcHandoverRequestMessage( (SupplicantStatus status, ArrayList 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 result = new SupplicantResult( "getNfcHandoverSelect()"); try { mISupplicantP2pIface.createNfcHandoverSelectMessage( (SupplicantStatus status, ArrayList 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 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 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 result = new SupplicantResult( "setClientList(" + networkId + ", " + clientListStr + ")"); try { ArrayList 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 result = new SupplicantResult( "getClientList(" + networkId + ")"); try { network.getClientList( (SupplicantStatus status, ArrayList clients) -> { result.setResult(status, clients); }); } catch (RemoteException e) { Log.e(TAG, "ISupplicantP2pIface exception: " + e); supplicantServiceDiedHandler(); } if (!result.isSuccess()) { return null; } ArrayList 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 result = new SupplicantResult("saveConfig()"); try { result.setResult(mISupplicantP2pIface.saveConfig()); } 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 { 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); } public E getResult() { return (isSuccess() ? mValue : null); } } }