/* * Copyright (C) 2017 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.server.wifi.p2p; import android.annotation.NonNull; import android.hardware.wifi.supplicant.V1_0.ISupplicantP2pIfaceCallback; import android.hardware.wifi.supplicant.V1_0.WpsConfigMethods; 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.WifiP2pProvDiscEvent; import android.net.wifi.p2p.WifiP2pWfdInfo; import android.net.wifi.p2p.nsd.WifiP2pServiceResponse; import android.util.Log; import com.android.server.wifi.p2p.WifiP2pServiceImpl.P2pStatus; import com.android.server.wifi.util.NativeUtil; import java.util.ArrayList; import java.util.Arrays; import java.util.List; /** * Class used for processing all P2P callbacks. */ public class SupplicantP2pIfaceCallbackImpl extends ISupplicantP2pIfaceCallback.Stub { private static final String TAG = "SupplicantP2pIfaceCallbackImpl"; private static boolean sVerboseLoggingEnabled = true; private final SupplicantP2pIfaceHal mP2pIfaceHal; private final String mInterface; private final WifiP2pMonitor mMonitor; public SupplicantP2pIfaceCallbackImpl( @NonNull SupplicantP2pIfaceHal p2pIfaceHal, @NonNull String iface, @NonNull WifiP2pMonitor monitor) { mP2pIfaceHal = p2pIfaceHal; mInterface = iface; mMonitor = monitor; } /** * Enable verbose logging for all sub modules. */ public static void enableVerboseLogging(int verbose) { sVerboseLoggingEnabled = verbose > 0; } protected static void logd(String s) { if (sVerboseLoggingEnabled) Log.d(TAG, s); } /** * Used to indicate that a new network has been added. * * @param networkId Network ID allocated to the corresponding network. */ public void onNetworkAdded(int networkId) { } /** * Used to indicate that a network has been removed. * * @param networkId Network ID allocated to the corresponding network. */ public void onNetworkRemoved(int networkId) { } /** * Used to indicate that a P2P device has been found. * * @param srcAddress MAC address of the device found. This must either * be the P2P device address or the P2P interface address. * @param p2pDeviceAddress P2P device address. * @param primaryDeviceType Type of device. Refer to section B.1 of Wifi P2P * Technical specification v1.2. * @param deviceName Name of the device. * @param configMethods Mask of WPS configuration methods supported by the * device. * @param deviceCapabilities Refer to section 4.1.4 of Wifi P2P Technical * specification v1.2. * @param groupCapabilities Refer to section 4.1.4 of Wifi P2P Technical * specification v1.2. * @param wfdDeviceInfo WFD device info as described in section 5.1.2 of WFD * technical specification v1.0.0. */ public void onDeviceFound(byte[] srcAddress, byte[] p2pDeviceAddress, byte[] primaryDeviceType, String deviceName, short configMethods, byte deviceCapabilities, int groupCapabilities, byte[] wfdDeviceInfo) { WifiP2pDevice device = new WifiP2pDevice(); device.deviceName = deviceName; if (deviceName == null) { Log.e(TAG, "Missing device name."); return; } try { device.deviceAddress = NativeUtil.macAddressFromByteArray(p2pDeviceAddress); } catch (Exception e) { Log.e(TAG, "Could not decode device address.", e); return; } try { device.primaryDeviceType = NativeUtil.wpsDevTypeStringFromByteArray(primaryDeviceType); } catch (Exception e) { Log.e(TAG, "Could not encode device primary type.", e); return; } device.deviceCapability = deviceCapabilities; device.groupCapability = groupCapabilities; device.wpsConfigMethodsSupported = configMethods; device.status = WifiP2pDevice.AVAILABLE; if (wfdDeviceInfo != null && wfdDeviceInfo.length >= 6) { device.wfdInfo = new WifiP2pWfdInfo( ((wfdDeviceInfo[0] & 0xFF) << 8) + (wfdDeviceInfo[1] & 0xFF), ((wfdDeviceInfo[2] & 0xFF) << 8) + (wfdDeviceInfo[3] & 0xFF), ((wfdDeviceInfo[4] & 0xFF) << 8) + (wfdDeviceInfo[5] & 0xFF)); } logd("Device discovered on " + mInterface + ": " + device); mMonitor.broadcastP2pDeviceFound(mInterface, device); } /** * Used to indicate that a P2P device has been lost. * * @param p2pDeviceAddress P2P device address. */ public void onDeviceLost(byte[] p2pDeviceAddress) { WifiP2pDevice device = new WifiP2pDevice(); try { device.deviceAddress = NativeUtil.macAddressFromByteArray(p2pDeviceAddress); } catch (Exception e) { Log.e(TAG, "Could not decode device address.", e); return; } device.status = WifiP2pDevice.UNAVAILABLE; logd("Device lost on " + mInterface + ": " + device); mMonitor.broadcastP2pDeviceLost(mInterface, device); } /** * Used to indicate the termination of P2P find operation. */ public void onFindStopped() { logd("Search stopped on " + mInterface); mMonitor.broadcastP2pFindStopped(mInterface); } /** * Used to indicate the reception of a P2P Group Owner negotiation request. * * @param srcAddress MAC address of the device that initiated the GO * negotiation request. * @param passwordId Type of password. */ public void onGoNegotiationRequest(byte[] srcAddress, short passwordId) { WifiP2pConfig config = new WifiP2pConfig(); try { config.deviceAddress = NativeUtil.macAddressFromByteArray(srcAddress); } catch (Exception e) { Log.e(TAG, "Could not decode device address.", e); return; } config.wps = new WpsInfo(); switch (passwordId) { case WpsDevPasswordId.USER_SPECIFIED: config.wps.setup = WpsInfo.DISPLAY; break; case WpsDevPasswordId.PUSHBUTTON: config.wps.setup = WpsInfo.PBC; break; case WpsDevPasswordId.REGISTRAR_SPECIFIED: config.wps.setup = WpsInfo.KEYPAD; break; default: config.wps.setup = WpsInfo.PBC; break; } logd("Group Owner negotiation initiated on " + mInterface + ": " + config); mMonitor.broadcastP2pGoNegotiationRequest(mInterface, config); } /** * Used to indicate the completion of a P2P Group Owner negotiation request. * * @param status Status of the GO negotiation. */ public void onGoNegotiationCompleted(int status) { logd("Group Owner negotiation completed with status: " + status); P2pStatus result = halStatusToP2pStatus(status); if (result == P2pStatus.SUCCESS) { mMonitor.broadcastP2pGoNegotiationSuccess(mInterface); } else { mMonitor.broadcastP2pGoNegotiationFailure(mInterface, result); } } /** * Used to indicate a successful formation of a P2P group. */ public void onGroupFormationSuccess() { logd("Group formation successful on " + mInterface); mMonitor.broadcastP2pGroupFormationSuccess(mInterface); } /** * Used to indicate a failure to form a P2P group. * * @param failureReason Failure reason string for debug purposes. */ public void onGroupFormationFailure(String failureReason) { // TODO(ender): failureReason should probably be an int (P2pStatusCode). logd("Group formation failed on " + mInterface + ": " + failureReason); mMonitor.broadcastP2pGroupFormationFailure(mInterface, failureReason); } /** * Used to indicate the start of a P2P group. * * @param groupIfName Interface name of the group. (For ex: p2p-p2p0-1) * @param isGo Whether this device is owner of the group. * @param ssid SSID of the group. * @param frequency Frequency on which this group is created. * @param psk PSK used to secure the group. * @param passphrase PSK passphrase used to secure the group. * @param goDeviceAddress MAC Address of the owner of this group. * @param isPersistent Whether this group is persisted or not. */ public void onGroupStarted(String groupIfName, boolean isGo, ArrayList ssid, int frequency, byte[] psk, String passphrase, byte[] goDeviceAddress, boolean isPersistent) { if (groupIfName == null) { Log.e(TAG, "Missing group interface name."); return; } logd("Group " + groupIfName + " started on " + mInterface); WifiP2pGroup group = new WifiP2pGroup(); group.setInterface(groupIfName); try { String quotedSsid = NativeUtil.encodeSsid(ssid); group.setNetworkName(NativeUtil.removeEnclosingQuotes(quotedSsid)); } catch (Exception e) { Log.e(TAG, "Could not encode SSID.", e); return; } group.setFrequency(frequency); group.setIsGroupOwner(isGo); group.setPassphrase(passphrase); if (isPersistent) { group.setNetworkId(WifiP2pGroup.NETWORK_ID_PERSISTENT); } else { group.setNetworkId(WifiP2pGroup.NETWORK_ID_TEMPORARY); } WifiP2pDevice owner = new WifiP2pDevice(); try { owner.deviceAddress = NativeUtil.macAddressFromByteArray(goDeviceAddress); } catch (Exception e) { Log.e(TAG, "Could not decode Group Owner address.", e); return; } group.setOwner(owner); mMonitor.broadcastP2pGroupStarted(mInterface, group); } /** * Used to indicate the removal of a P2P group. * * @param groupIfName Interface name of the group. (For ex: p2p-p2p0-1) * @param isGo Whether this device is owner of the group. */ public void onGroupRemoved(String groupIfName, boolean isGo) { if (groupIfName == null) { Log.e(TAG, "Missing group name."); return; } logd("Group " + groupIfName + " removed from " + mInterface); WifiP2pGroup group = new WifiP2pGroup(); group.setInterface(groupIfName); group.setIsGroupOwner(isGo); mMonitor.broadcastP2pGroupRemoved(mInterface, group); } /** * Used to indicate the reception of a P2P invitation. * * @param srcAddress MAC address of the device that sent the invitation. * @param goDeviceAddress MAC Address of the owner of this group. * @param bssid Bssid of the group. * @param persistentNetworkId Persistent network Id of the group. * @param operatingFrequency Frequency on which the invitation was received. */ public void onInvitationReceived(byte[] srcAddress, byte[] goDeviceAddress, byte[] bssid, int persistentNetworkId, int operatingFrequency) { WifiP2pGroup group = new WifiP2pGroup(); group.setNetworkId(persistentNetworkId); WifiP2pDevice client = new WifiP2pDevice(); try { client.deviceAddress = NativeUtil.macAddressFromByteArray(srcAddress); } catch (Exception e) { Log.e(TAG, "Could not decode MAC address.", e); return; } group.addClient(client); WifiP2pDevice owner = new WifiP2pDevice(); try { owner.deviceAddress = NativeUtil.macAddressFromByteArray(goDeviceAddress); } catch (Exception e) { Log.e(TAG, "Could not decode Group Owner MAC address.", e); return; } group.setOwner(owner); logd("Invitation received on " + mInterface + ": " + group); mMonitor.broadcastP2pInvitationReceived(mInterface, group); } /** * Used to indicate the result of the P2P invitation request. * * @param bssid Bssid of the group. * @param status Status of the invitation. */ public void onInvitationResult(byte[] bssid, int status) { logd("Invitation completed with status: " + status); mMonitor.broadcastP2pInvitationResult(mInterface, halStatusToP2pStatus(status)); } /** * Used to indicate the completion of a P2P provision discovery request. * * @param p2pDeviceAddress P2P device address. * @param isRequest Whether we received or sent the provision discovery. * @param status Status of the provision discovery (SupplicantStatusCode). * @param configMethods Mask of WPS configuration methods supported. * Only one configMethod bit should be set per call. * @param generatedPin 8 digit pin generated. */ public void onProvisionDiscoveryCompleted(byte[] p2pDeviceAddress, boolean isRequest, byte status, short configMethods, String generatedPin) { if (status != ISupplicantP2pIfaceCallback.P2pProvDiscStatusCode.SUCCESS) { Log.e(TAG, "Provision discovery failed: " + status); mMonitor.broadcastP2pProvisionDiscoveryFailure(mInterface); return; } logd("Provision discovery " + (isRequest ? "request" : "response") + " for WPS Config method: " + configMethods); WifiP2pProvDiscEvent event = new WifiP2pProvDiscEvent(); event.device = new WifiP2pDevice(); try { event.device.deviceAddress = NativeUtil.macAddressFromByteArray(p2pDeviceAddress); } catch (Exception e) { Log.e(TAG, "Could not decode MAC address.", e); return; } if ((configMethods & WpsConfigMethods.PUSHBUTTON) != 0) { if (isRequest) { event.event = WifiP2pProvDiscEvent.PBC_REQ; mMonitor.broadcastP2pProvisionDiscoveryPbcRequest(mInterface, event); } else { event.event = WifiP2pProvDiscEvent.PBC_RSP; mMonitor.broadcastP2pProvisionDiscoveryPbcResponse(mInterface, event); } } else if (!isRequest && (configMethods & WpsConfigMethods.KEYPAD) != 0) { event.event = WifiP2pProvDiscEvent.SHOW_PIN; event.pin = generatedPin; mMonitor.broadcastP2pProvisionDiscoveryShowPin(mInterface, event); } else if (!isRequest && (configMethods & WpsConfigMethods.DISPLAY) != 0) { event.event = WifiP2pProvDiscEvent.ENTER_PIN; mMonitor.broadcastP2pProvisionDiscoveryEnterPin(mInterface, event); } else if (isRequest && (configMethods & WpsConfigMethods.DISPLAY) != 0) { event.event = WifiP2pProvDiscEvent.SHOW_PIN; event.pin = generatedPin; mMonitor.broadcastP2pProvisionDiscoveryShowPin(mInterface, event); } else if (isRequest && (configMethods & WpsConfigMethods.KEYPAD) != 0) { event.event = WifiP2pProvDiscEvent.ENTER_PIN; mMonitor.broadcastP2pProvisionDiscoveryEnterPin(mInterface, event); } else { Log.e(TAG, "Unsupported config methods: " + configMethods); } } /** * Used to indicate the reception of a P2P service discovery response. * * @param srcAddress MAC address of the device that sent the service discovery. * @param updateIndicator Service update indicator. Refer to section 3.1.3 of * Wifi P2P Technical specification v1.2. * @param tlvs Refer to section 3.1.3.1 of Wifi P2P Technical specification v1.2. */ public void onServiceDiscoveryResponse(byte[] srcAddress, short updateIndicator, ArrayList tlvs) { List response = null; logd("Service discovery response received on " + mInterface); try { String srcAddressStr = NativeUtil.macAddressFromByteArray(srcAddress); // updateIndicator is not used response = WifiP2pServiceResponse.newInstance(srcAddressStr, NativeUtil.byteArrayFromArrayList(tlvs)); } catch (Exception e) { Log.e(TAG, "Could not process service discovery response.", e); return; } mMonitor.broadcastP2pServiceDiscoveryResponse(mInterface, response); } private WifiP2pDevice createStaEventDevice(byte[] srcAddress, byte[] p2pDeviceAddress) { WifiP2pDevice device = new WifiP2pDevice(); byte[] deviceAddressBytes; // Legacy STAs may not supply a p2pDeviceAddress (signaled by a zero'd p2pDeviceAddress) // In this case, use srcAddress instead if (!Arrays.equals(NativeUtil.ANY_MAC_BYTES, p2pDeviceAddress)) { deviceAddressBytes = p2pDeviceAddress; } else { deviceAddressBytes = srcAddress; } try { device.deviceAddress = NativeUtil.macAddressFromByteArray(deviceAddressBytes); } catch (Exception e) { Log.e(TAG, "Could not decode MAC address", e); return null; } return device; } /** * Used to indicate when a STA device is connected to this device. * * @param srcAddress MAC address of the device that was authorized. * @param p2pDeviceAddress P2P device address. */ public void onStaAuthorized(byte[] srcAddress, byte[] p2pDeviceAddress) { logd("STA authorized on " + mInterface); WifiP2pDevice device = createStaEventDevice(srcAddress, p2pDeviceAddress); if (device == null) { return; } mMonitor.broadcastP2pApStaConnected(mInterface, device); } /** * Used to indicate when a STA device is disconnected from this device. * * @param srcAddress MAC address of the device that was deauthorized. * @param p2pDeviceAddress P2P device address. */ public void onStaDeauthorized(byte[] srcAddress, byte[] p2pDeviceAddress) { logd("STA deauthorized on " + mInterface); WifiP2pDevice device = createStaEventDevice(srcAddress, p2pDeviceAddress); if (device == null) { return; } mMonitor.broadcastP2pApStaDisconnected(mInterface, device); } private static P2pStatus halStatusToP2pStatus(int status) { P2pStatus result = P2pStatus.UNKNOWN; switch (status) { case P2pStatusCode.SUCCESS: case P2pStatusCode.SUCCESS_DEFERRED: result = P2pStatus.SUCCESS; break; case P2pStatusCode.FAIL_INFO_CURRENTLY_UNAVAILABLE: result = P2pStatus.INFORMATION_IS_CURRENTLY_UNAVAILABLE; break; case P2pStatusCode.FAIL_INCOMPATIBLE_PARAMS: result = P2pStatus.INCOMPATIBLE_PARAMETERS; break; case P2pStatusCode.FAIL_LIMIT_REACHED: result = P2pStatus.LIMIT_REACHED; break; case P2pStatusCode.FAIL_INVALID_PARAMS: result = P2pStatus.INVALID_PARAMETER; break; case P2pStatusCode.FAIL_UNABLE_TO_ACCOMMODATE: result = P2pStatus.UNABLE_TO_ACCOMMODATE_REQUEST; break; case P2pStatusCode.FAIL_PREV_PROTOCOL_ERROR: result = P2pStatus.PREVIOUS_PROTOCOL_ERROR; break; case P2pStatusCode.FAIL_NO_COMMON_CHANNELS: result = P2pStatus.NO_COMMON_CHANNEL; break; case P2pStatusCode.FAIL_UNKNOWN_GROUP: result = P2pStatus.UNKNOWN_P2P_GROUP; break; case P2pStatusCode.FAIL_BOTH_GO_INTENT_15: result = P2pStatus.BOTH_GO_INTENT_15; break; case P2pStatusCode.FAIL_INCOMPATIBLE_PROV_METHOD: result = P2pStatus.INCOMPATIBLE_PROVISIONING_METHOD; break; case P2pStatusCode.FAIL_REJECTED_BY_USER: result = P2pStatus.REJECTED_BY_USER; break; } return result; } }