diff options
author | 2024-11-25 06:23:25 +0000 | |
---|---|---|
committer | 2024-12-03 20:13:20 +0000 | |
commit | 4805ca200f1d5b63e3f6c5fddcd78fc487b4224b (patch) | |
tree | 8e71a9241d3d78c47cae6597012e5c41364f8dd6 | |
parent | db6a1f64880dcf3d59c99a7ecf8f6d7cae7edad8 (diff) |
Support for BLE assisted P2P discovery and pairing
This CL includes,
1. An interface to generate a DIR info and to validate a received
DIR info.
2. Added a new bootstrapping method which
will be set for bootsrapping done over BLE.
3. Added authorize flag in WifiP2pConfig for the device to authorize a
connection request from Peer.
4. Added API to get the BSSID of group owner.
Bug: 341971059
Flag: com.android.wifi.flags.wifi_direct_r2
Test: Manual - Basic P2P connect/disconnect tests
Test: TH Presubmit tests
Test: atest com.android.server.wifi.p2p
Change-Id: I2f602f4ea1cb383501967d0bfae8e3b2fa55ec95
11 files changed, 661 insertions, 5 deletions
diff --git a/framework/api/current.txt b/framework/api/current.txt index a4982793a5..451f907e19 100644 --- a/framework/api/current.txt +++ b/framework/api/current.txt @@ -1280,6 +1280,7 @@ package android.net.wifi.p2p { method @FlaggedApi("com.android.wifi.flags.wifi_direct_r2") @Nullable public android.net.wifi.p2p.WifiP2pPairingBootstrappingConfig getPairingBootstrappingConfig(); method @Nullable public String getPassphrase(); method @FlaggedApi("com.android.wifi.flags.wifi_direct_r2") public int getPccModeConnectionType(); + method @FlaggedApi("com.android.wifi.flags.wifi_direct_r2") public boolean isAuthorizeConnectionFromPeer(); method @FlaggedApi("com.android.wifi.flags.wifi_direct_r2") public void setGroupOwnerVersion(int); method public void writeToParcel(android.os.Parcel, int); field @NonNull public static final android.os.Parcelable.Creator<android.net.wifi.p2p.WifiP2pConfig> CREATOR; @@ -1306,6 +1307,7 @@ package android.net.wifi.p2p { ctor public WifiP2pConfig.Builder(); method @NonNull public android.net.wifi.p2p.WifiP2pConfig build(); method @NonNull public android.net.wifi.p2p.WifiP2pConfig.Builder enablePersistentMode(boolean); + method @FlaggedApi("com.android.wifi.flags.wifi_direct_r2") @NonNull public android.net.wifi.p2p.WifiP2pConfig.Builder setAuthorizeConnectionFromPeer(boolean); method @NonNull public android.net.wifi.p2p.WifiP2pConfig.Builder setDeviceAddress(@Nullable android.net.MacAddress); method @NonNull public android.net.wifi.p2p.WifiP2pConfig.Builder setGroupClientIpProvisioningMode(int); method @NonNull public android.net.wifi.p2p.WifiP2pConfig.Builder setGroupOperatingBand(int); @@ -1358,6 +1360,16 @@ package android.net.wifi.p2p { field @NonNull public static final android.os.Parcelable.Creator<android.net.wifi.p2p.WifiP2pDeviceList> CREATOR; } + @FlaggedApi("com.android.wifi.flags.wifi_direct_r2") public final class WifiP2pDirInfo implements android.os.Parcelable { + ctor public WifiP2pDirInfo(@NonNull android.net.MacAddress, @NonNull byte[], @NonNull byte[]); + method public int describeContents(); + method @NonNull public byte[] getDirTag(); + method @NonNull public android.net.MacAddress getMacAddress(); + method @NonNull public byte[] getNonce(); + method public void writeToParcel(@NonNull android.os.Parcel, int); + field @NonNull public static final android.os.Parcelable.Creator<android.net.wifi.p2p.WifiP2pDirInfo> CREATOR; + } + @FlaggedApi("com.android.wifi.flags.android_v_wifi_api") public final class WifiP2pDiscoveryConfig implements android.os.Parcelable { method @FlaggedApi("com.android.wifi.flags.android_v_wifi_api") public int describeContents(); method @FlaggedApi("com.android.wifi.flags.android_v_wifi_api") public int getFrequencyMhz(); @@ -1378,6 +1390,7 @@ package android.net.wifi.p2p { method public int describeContents(); method public java.util.Collection<android.net.wifi.p2p.WifiP2pDevice> getClientList(); method public int getFrequency(); + method @FlaggedApi("com.android.wifi.flags.wifi_direct_r2") @Nullable public android.net.MacAddress getGroupOwnerBssid(); method public String getInterface(); method public int getNetworkId(); method public String getNetworkName(); @@ -1438,6 +1451,7 @@ package android.net.wifi.p2p { method public void removeServiceRequest(android.net.wifi.p2p.WifiP2pManager.Channel, android.net.wifi.p2p.nsd.WifiP2pServiceRequest, android.net.wifi.p2p.WifiP2pManager.ActionListener); method public void requestConnectionInfo(android.net.wifi.p2p.WifiP2pManager.Channel, android.net.wifi.p2p.WifiP2pManager.ConnectionInfoListener); method @RequiresPermission(allOf={android.Manifest.permission.NEARBY_WIFI_DEVICES, android.Manifest.permission.ACCESS_FINE_LOCATION}, conditional=true) public void requestDeviceInfo(@NonNull android.net.wifi.p2p.WifiP2pManager.Channel, @NonNull android.net.wifi.p2p.WifiP2pManager.DeviceInfoListener); + method @FlaggedApi("com.android.wifi.flags.wifi_direct_r2") @RequiresPermission(allOf={android.Manifest.permission.NEARBY_WIFI_DEVICES, android.Manifest.permission.ACCESS_FINE_LOCATION}, conditional=true) public void requestDirInfo(@NonNull android.net.wifi.p2p.WifiP2pManager.Channel, @NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<android.net.wifi.p2p.WifiP2pDirInfo,java.lang.Exception>); method public void requestDiscoveryState(@NonNull android.net.wifi.p2p.WifiP2pManager.Channel, @NonNull android.net.wifi.p2p.WifiP2pManager.DiscoveryStateListener); method @RequiresPermission(allOf={android.Manifest.permission.NEARBY_WIFI_DEVICES, android.Manifest.permission.ACCESS_FINE_LOCATION}, conditional=true) public void requestGroupInfo(android.net.wifi.p2p.WifiP2pManager.Channel, android.net.wifi.p2p.WifiP2pManager.GroupInfoListener); method public void requestNetworkInfo(@NonNull android.net.wifi.p2p.WifiP2pManager.Channel, @NonNull android.net.wifi.p2p.WifiP2pManager.NetworkInfoListener); @@ -1456,6 +1470,7 @@ package android.net.wifi.p2p { method public void stopListening(@NonNull android.net.wifi.p2p.WifiP2pManager.Channel, @Nullable android.net.wifi.p2p.WifiP2pManager.ActionListener); method public void stopPeerDiscovery(android.net.wifi.p2p.WifiP2pManager.Channel, android.net.wifi.p2p.WifiP2pManager.ActionListener); method @FlaggedApi("com.android.wifi.flags.android_v_wifi_api") public void unregisterWifiP2pListener(@NonNull android.net.wifi.p2p.WifiP2pManager.WifiP2pListener); + method @FlaggedApi("com.android.wifi.flags.wifi_direct_r2") @RequiresPermission(allOf={android.Manifest.permission.NEARBY_WIFI_DEVICES, android.Manifest.permission.ACCESS_FINE_LOCATION}, conditional=true) public void validateDirInfo(@NonNull android.net.wifi.p2p.WifiP2pManager.Channel, @NonNull android.net.wifi.p2p.WifiP2pDirInfo, @NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<java.lang.Boolean,java.lang.Exception>); field public static final String ACTION_WIFI_P2P_LISTEN_STATE_CHANGED = "android.net.wifi.p2p.action.WIFI_P2P_LISTEN_STATE_CHANGED"; field public static final String ACTION_WIFI_P2P_REQUEST_RESPONSE_CHANGED = "android.net.wifi.p2p.action.WIFI_P2P_REQUEST_RESPONSE_CHANGED"; field public static final int BUSY = 2; // 0x2 @@ -1480,6 +1495,7 @@ package android.net.wifi.p2p { field @FlaggedApi("com.android.wifi.flags.android_v_wifi_api") public static final int GROUP_CREATION_FAILURE_REASON_PROVISION_DISCOVERY_FAILED = 3; // 0x3 field @FlaggedApi("com.android.wifi.flags.android_v_wifi_api") public static final int GROUP_CREATION_FAILURE_REASON_TIMED_OUT = 1; // 0x1 field @FlaggedApi("com.android.wifi.flags.android_v_wifi_api") public static final int GROUP_CREATION_FAILURE_REASON_USER_REJECTED = 2; // 0x2 + field @FlaggedApi("com.android.wifi.flags.wifi_direct_r2") public static final int NO_PERMISSION = 4; // 0x4 field public static final int NO_SERVICE_REQUESTS = 3; // 0x3 field public static final int P2P_UNSUPPORTED = 1; // 0x1 field public static final String WIFI_P2P_CONNECTION_CHANGED_ACTION = "android.net.wifi.p2p.CONNECTION_STATE_CHANGE"; @@ -1596,6 +1612,7 @@ package android.net.wifi.p2p { field public static final int PAIRING_BOOTSTRAPPING_METHOD_KEYPAD_PASSPHRASE = 16; // 0x10 field public static final int PAIRING_BOOTSTRAPPING_METHOD_KEYPAD_PINCODE = 8; // 0x8 field public static final int PAIRING_BOOTSTRAPPING_METHOD_OPPORTUNISTIC = 1; // 0x1 + field public static final int PAIRING_BOOTSTRAPPING_METHOD_OUT_OF_BAND = 32; // 0x20 } @FlaggedApi("com.android.wifi.flags.wifi_direct_r2") public final class WifiP2pUsdBasedLocalServiceAdvertisementConfig implements android.os.Parcelable { diff --git a/framework/java/android/net/wifi/p2p/WifiP2pConfig.java b/framework/java/android/net/wifi/p2p/WifiP2pConfig.java index d1bed9dc96..67f1e6cd3a 100644 --- a/framework/java/android/net/wifi/p2p/WifiP2pConfig.java +++ b/framework/java/android/net/wifi/p2p/WifiP2pConfig.java @@ -394,6 +394,27 @@ public class WifiP2pConfig implements Parcelable { return mPairingBootstrappingConfig; } + /** + * Used to authorize a connection request from the peer device. + */ + private boolean mAuthorizeConnectionFromPeer = false; + + /** + * Query to check if the configuration is for authorizing a connection request + * from the peer device. @see {@link Builder#setAuthorizeConnectionFromPeer(boolean)} + * + * @return true if configured to authorize a connection request from the Peer device, + * False otherwise. + */ + @RequiresApi(Build.VERSION_CODES.BAKLAVA) + @FlaggedApi(Flags.FLAG_WIFI_DIRECT_R2) + public boolean isAuthorizeConnectionFromPeer() { + if (!Environment.isSdkAtLeastB()) { + throw new UnsupportedOperationException(); + } + return mAuthorizeConnectionFromPeer; + } + public WifiP2pConfig() { //set defaults wps = new WpsInfo(); @@ -477,6 +498,7 @@ public class WifiP2pConfig implements Parcelable { .append((mPairingBootstrappingConfig == null) ? "<null>" : mPairingBootstrappingConfig.toString()); } + sbuf.append("\n authorizeConnectionFromPeer: ").append(mAuthorizeConnectionFromPeer); return sbuf.toString(); } @@ -501,6 +523,7 @@ public class WifiP2pConfig implements Parcelable { mVendorData = new ArrayList<>(source.mVendorData); mGroupOwnerVersion = source.mGroupOwnerVersion; mPairingBootstrappingConfig = source.mPairingBootstrappingConfig; + mAuthorizeConnectionFromPeer = source.mAuthorizeConnectionFromPeer; } } @@ -521,6 +544,7 @@ public class WifiP2pConfig implements Parcelable { if (Environment.isSdkAtLeastB() && Flags.wifiDirectR2()) { dest.writeParcelable(mPairingBootstrappingConfig, flags); } + dest.writeBoolean(mAuthorizeConnectionFromPeer); } /** Implement the Parcelable interface */ @@ -545,6 +569,7 @@ public class WifiP2pConfig implements Parcelable { config.mPairingBootstrappingConfig = in.readParcelable( WifiP2pPairingBootstrappingConfig.class.getClassLoader()); } + config.mAuthorizeConnectionFromPeer = in.readBoolean(); return config; } @@ -585,6 +610,7 @@ public class WifiP2pConfig implements Parcelable { @PccModeConnectionType private int mPccModeConnectionType = PCC_MODE_DEFAULT_CONNECTION_TYPE_LEGACY_ONLY; private @Nullable WifiP2pPairingBootstrappingConfig mPairingBootstrappingConfig; + private boolean mAuthorizeConnectionFromPeer = false; /** * Specify the peer's MAC address. If not set, the device will @@ -913,6 +939,34 @@ public class WifiP2pConfig implements Parcelable { } /** + * Specify that the configuration is to authorize a connection request from a peer device. + * The MAC address of the peer device is specified using + * {@link WifiP2pConfig.Builder#setDeviceAddress(MacAddress)}. + * <p> + * Optional. false by default. The default configuration is to join a group or to initiate + * a group formation. + * <p> + * This configuration is typically used in Bluetooth LE assisted P2P pairing protocol + * defined in Wi-Fi Direct R2 specification, section 3.9. The collocated Bluetooth Provider + * sends the pairing password to the peer device (Seeker) and direct the system to + * authorize the connection request from the peer device using {@link + * WifiP2pManager#connect(WifiP2pManager.Channel, WifiP2pConfig, + * WifiP2pManager.ActionListener)}. The device will then wait for the connection request + * from the peer device. + * + * @param authorize true to authorize a connection request from the peer device, false to + * let the device join a group or form a group. + * @return The builder to facilitate chaining {@code builder.setXXX(..).setXXX(..)}. + */ + @RequiresApi(Build.VERSION_CODES.BAKLAVA) + @FlaggedApi(Flags.FLAG_WIFI_DIRECT_R2) + @NonNull + public Builder setAuthorizeConnectionFromPeer(boolean authorize) { + mAuthorizeConnectionFromPeer = authorize; + return this; + } + + /** * Build {@link WifiP2pConfig} given the current requests made on the builder. * @return {@link WifiP2pConfig} constructed based on builder method calls. */ @@ -963,6 +1017,7 @@ public class WifiP2pConfig implements Parcelable { config.mGroupClientIpProvisioningMode = mGroupClientIpProvisioningMode; config.mJoinExistingGroup = mJoinExistingGroup; config.mPairingBootstrappingConfig = mPairingBootstrappingConfig; + config.mAuthorizeConnectionFromPeer = mAuthorizeConnectionFromPeer; return config; } } diff --git a/framework/java/android/net/wifi/p2p/WifiP2pDirInfo.java b/framework/java/android/net/wifi/p2p/WifiP2pDirInfo.java new file mode 100644 index 0000000000..0292628154 --- /dev/null +++ b/framework/java/android/net/wifi/p2p/WifiP2pDirInfo.java @@ -0,0 +1,146 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.net.wifi.p2p; + +import android.annotation.FlaggedApi; +import android.annotation.NonNull; +import android.annotation.RequiresApi; +import android.net.MacAddress; +import android.os.Build; +import android.os.Parcel; +import android.os.Parcelable; + +import com.android.wifi.flags.Flags; + +import java.util.Arrays; + +/** + * This object contains the Device Identity Resolution (DIR) Info to check if the device is a + * previously paired device. + * The device advertises this information in Bluetooth LE advertising packets + * and Unsynchronized Service Discovery (USD) frames. The device receiving DIR + * Info uses this information to identify that the peer device is a previously paired device. + * For Details, refer Wi-Fi Alliance Wi-Fi Direct R2 specification section 3.8.2 Pairing Identity + * and section 3.9.2.3.2 Optional Advertising Data Elements. + */ +@RequiresApi(Build.VERSION_CODES.BAKLAVA) +@FlaggedApi(Flags.FLAG_WIFI_DIRECT_R2) +public final class WifiP2pDirInfo implements Parcelable { + /** + * The MAC address of the P2P device interface. + */ + private MacAddress mMacAddress; + + /** + * Random number of 8 octets. + */ + private byte[] mNonce; + + /** + * A resolvable identity value of 8 octets. + */ + private byte[] mDirTag; + + /** + * @return the MAC address of the P2P device interface. + */ + @NonNull + public MacAddress getMacAddress() { + return mMacAddress; + } + + /** + * Get the nonce value used to derive DIR Tag. + * See {@link WifiP2pDirInfo} + * + * @return A byte-array of random number of size 8 octets. + */ + @NonNull + public byte[] getNonce() { + return mNonce; + } + + /** + * Get the DIR Tag value. + * See {@link WifiP2pDirInfo} + * + * @return A byte-array of Tag value of size 8 octets. + */ + @NonNull + public byte[] getDirTag() { + return mDirTag; + } + + /** + * Constructor for Device Identity Resolution (DIR) Info generated based on the 128 bit Device + * Identity key. For details, refer Wi-Fi Alliance Wi-Fi Direct R2 specification Table 8. + * + * @param macAddress The MAC address of the P2P device interface. + * @param nonce Random number of 8 octets. + * @param dirTag Resolvable identity value of 8 octets derived based on the device MAC address, + * device identity key and P2P device MAC address. + * Tag = Truncate-64(HMAC-SHA-256(DevIk, "DIR" || P2P Device Address || Nonce)) + * + */ + public WifiP2pDirInfo(@NonNull MacAddress macAddress, @NonNull byte[] nonce, + @NonNull byte[] dirTag) { + mMacAddress = macAddress; + mNonce = nonce; + mDirTag = dirTag; + } + + /** + * Generates a string of all the defined elements. + * + * @return a compiled string representing all elements + */ + public String toString() { + StringBuilder sbuf = new StringBuilder("WifiP2pDirInfo:"); + sbuf.append("\n Mac Address: ").append(mMacAddress); + sbuf.append("\n Nonce : ").append((mNonce == null) + ? "<null>" : Arrays.toString(mNonce)); + sbuf.append("\n DIR Tag : ").append((mDirTag == null) + ? "<null>" : Arrays.toString(mDirTag)); + return sbuf.toString(); + } + + /** Implement the Parcelable interface */ + public int describeContents() { + return 0; + } + + /** Implement the Parcelable interface */ + public void writeToParcel(@NonNull Parcel dest, int flags) { + mMacAddress.writeToParcel(dest, flags); + dest.writeByteArray(mNonce); + dest.writeByteArray(mDirTag); + } + + /** Implement the Parcelable interface */ + @NonNull + public static final Creator<WifiP2pDirInfo> CREATOR = + new Creator<WifiP2pDirInfo>() { + public WifiP2pDirInfo createFromParcel(Parcel in) { + return new WifiP2pDirInfo(MacAddress.CREATOR.createFromParcel(in), + in.createByteArray(), in.createByteArray()); + } + + public WifiP2pDirInfo[] newArray(int size) { + return new WifiP2pDirInfo[size]; + } + }; +} diff --git a/framework/java/android/net/wifi/p2p/WifiP2pGroup.java b/framework/java/android/net/wifi/p2p/WifiP2pGroup.java index eb51cc0fdd..43dc26fa04 100644 --- a/framework/java/android/net/wifi/p2p/WifiP2pGroup.java +++ b/framework/java/android/net/wifi/p2p/WifiP2pGroup.java @@ -500,6 +500,31 @@ public class WifiP2pGroup implements Parcelable { mVendorData = new ArrayList<>(vendorData); } + /** + * Returns the BSSID, if this device is the group owner of the P2P group supporting Wi-Fi + * Direct R2 protocol. + * <p> + * The interface address of a Wi-Fi Direct R2 supported device is randomized. So for every + * group owner session a randomized interface address will be returned. + * <p> + * The BSSID returned will be {@code null}, if this device is a client device or a group owner + * which doesn't support Wi-Fi Direct R2 protocol. + * @return the BSSID. + */ + @RequiresApi(Build.VERSION_CODES.BAKLAVA) + @FlaggedApi(Flags.FLAG_WIFI_DIRECT_R2) + @Nullable + public MacAddress getGroupOwnerBssid() { + if (!Environment.isSdkAtLeastB()) { + throw new UnsupportedOperationException(); + } + if (isGroupOwner() && getSecurityType() == SECURITY_TYPE_WPA3_SAE + && interfaceAddress != null) { + return MacAddress.fromBytes(interfaceAddress); + } + return null; + } + public String toString() { StringBuffer sbuf = new StringBuffer(); sbuf.append("network: ").append(mNetworkName); diff --git a/framework/java/android/net/wifi/p2p/WifiP2pManager.java b/framework/java/android/net/wifi/p2p/WifiP2pManager.java index 80ec9631f1..717bde6a80 100644 --- a/framework/java/android/net/wifi/p2p/WifiP2pManager.java +++ b/framework/java/android/net/wifi/p2p/WifiP2pManager.java @@ -51,6 +51,7 @@ import android.os.Handler; import android.os.Looper; import android.os.Message; import android.os.Messenger; +import android.os.OutcomeReceiver; import android.os.RemoteException; import android.text.TextUtils; import android.util.CloseGuard; @@ -629,6 +630,13 @@ public class WifiP2pManager { */ public static final int WIFI_P2P_USD_BASED_ADD_LOCAL_SERVICE = 1; + /** + * Extra for transporting DIR Information. + * @hide + */ + public static final String EXTRA_PARAM_KEY_DIR_INFO = + "android.net.wifi.p2p.EXTRA_PARAM_KEY_DIR_INFO"; + private Context mContext; IWifiP2pManager mService; @@ -904,6 +912,20 @@ public class WifiP2pManager { /** @hide */ public static final int RESPONSE_GET_LISTEN_STATE = BASE + 118; + /** @hide */ + public static final int GET_DIR_INFO = BASE + 119; + /** @hide */ + public static final int GET_DIR_INFO_FAILED = BASE + 120; + /** @hide */ + public static final int RESPONSE_GET_DIR_INFO = BASE + 121; + + /** @hide */ + public static final int VALIDATE_DIR_INFO = BASE + 122; + /** @hide */ + public static final int VALIDATE_DIR_INFO_FAILED = BASE + 123; + /** @hide */ + public static final int RESPONSE_VALIDATE_DIR_INFO = BASE + 124; + private static final SparseArray<IWifiP2pListener> sWifiP2pListenerMap = new SparseArray<>(); /** * Create a new WifiP2pManager instance. Applications use @@ -945,6 +967,14 @@ public class WifiP2pManager { */ public static final int NO_SERVICE_REQUESTS = 3; + /** + * Passed with {@link ActionListener#onFailure}. + * Indicates that the operation failed due to calling app doesn't have permission to call the + * API. + */ + @FlaggedApi(Flags.FLAG_WIFI_DIRECT_R2) + public static final int NO_PERMISSION = 4; + /** Interface for callback invocation when framework channel is lost */ public interface ChannelListener { /** @@ -1950,6 +1980,31 @@ public class WifiP2pManager { .onPinGenerated(deviceAddress, pin); } break; + case RESPONSE_GET_DIR_INFO: + if (listener != null) { + if (Flags.wifiDirectR2()) { + ((WifiP2pDirInfoListener) listener) + .onDirInfoReceived((WifiP2pDirInfo) message.obj); + } + } + break; + case GET_DIR_INFO_FAILED: + if (listener != null) { + ((WifiP2pDirInfoListener) listener) + .onFailure(message.arg1); + } + break; + case RESPONSE_VALIDATE_DIR_INFO: + if (listener != null) { + ((WifiP2pDirInfoValidationListener) listener) + .onDirInfoValidation(message.arg1 == 1); + } + break; + case VALIDATE_DIR_INFO_FAILED: + if (listener != null) { + ((WifiP2pDirInfoValidationListener) listener) + .onFailure(message.arg1); + } default: Log.d(TAG, "Ignored " + message); break; @@ -3835,4 +3890,186 @@ public class WifiP2pManager { public static int getP2pMaxAllowedVendorElementsLengthBytes() { return WIFI_P2P_VENDOR_ELEMENTS_MAXIMUM_LENGTH; } + + private static Exception reasonCodeToException(int reason) { + if (reason == ERROR) { + return new IllegalStateException("Internal error"); + } else if (reason == BUSY) { + return new IllegalStateException("Framework is busy"); + } else if (Flags.wifiDirectR2() && reason == NO_PERMISSION) { + return new SecurityException("Application doesn't have required permission"); + } else { + return new IllegalStateException(); + } + } + + /** + * Interface for callback invocation in response to {@link #requestDirInfo}. + * @hide + */ + public interface WifiP2pDirInfoListener { + /** + * The callback to indicate that the system searched for DIR information. + * @param dirInfo {@link WifiP2pDirInfo} if exists, otherwise null. + */ + void onDirInfoReceived(@Nullable WifiP2pDirInfo dirInfo); + + /** + * The operation failed. + * @param reason The reason for failure. + */ + void onFailure(int reason); + } + + /** + * Get the Device Identity Resolution (DIR) Information. + * See {@link WifiP2pDirInfo} for details + * + * Note: The results callback returns null if the device doesn't have any persistent group + * with device identity key information. + * + * <p> + * Use {@link #isWiFiDirectR2Supported()} to determine whether the device supports + * this feature. If {@link #isWiFiDirectR2Supported()} return {@code false} then + * this method will throw {@link UnsupportedOperationException}. + * <p> + * The application must have {@link android.Manifest.permission#NEARBY_WIFI_DEVICES} with + * android:usesPermissionFlags="neverForLocation". If the application does not declare + * android:usesPermissionFlags="neverForLocation", then it must also have + * {@link android.Manifest.permission#ACCESS_FINE_LOCATION}. + * + * @param c It is the channel created at {@link #initialize}. + * @param executor The executor on which callback will be invoked. + * @param callback An OutcomeReceiver callback for receiving {@link WifiP2pDirInfo} via + * {@link OutcomeReceiver#onResult(Object)}. This callback will return + * null when DIR info doesn't exist. + * When this API call fails due to permission issues, state machine + * is busy etc., {@link OutcomeReceiver#onError(Throwable)} is called. + */ + @RequiresPermission(allOf = { + android.Manifest.permission.NEARBY_WIFI_DEVICES, + android.Manifest.permission.ACCESS_FINE_LOCATION + }, conditional = true) + @RequiresApi(Build.VERSION_CODES.BAKLAVA) + @FlaggedApi(Flags.FLAG_WIFI_DIRECT_R2) + public void requestDirInfo(@NonNull Channel c, @NonNull @CallbackExecutor Executor executor, + @NonNull OutcomeReceiver<WifiP2pDirInfo, Exception> callback) { + if (!Environment.isSdkAtLeastB()) { + throw new UnsupportedOperationException(); + } + if (!isWiFiDirectR2Supported()) { + throw new UnsupportedOperationException(); + } + Objects.requireNonNull(c, "channel cannot be null and needs to be initialized)"); + Objects.requireNonNull(executor, "executor cannot be null"); + Objects.requireNonNull(callback, "callback cannot be null"); + Bundle extras = prepareExtrasBundle(c); + c.mAsyncChannel.sendMessage(prepareMessage(GET_DIR_INFO, 0, + c.putListener(new WifiP2pDirInfoListener() { + @Override + public void onDirInfoReceived(WifiP2pDirInfo result) { + Binder.clearCallingIdentity(); + executor.execute(() -> { + callback.onResult(result); + }); + } + + @Override + public void onFailure(int reason) { + Binder.clearCallingIdentity(); + executor.execute(() -> { + callback.onError(reasonCodeToException(reason)); + }); + } + }), extras, c.mContext)); + } + + /** + * Interface for callback invocation when the received DIR information is validated + * in response to {@link #validateDirInfo}. + * @hide + */ + public interface WifiP2pDirInfoValidationListener { + /** + * The requested DIR information is validated. + * @param result True if a match is found, false otherwise. + */ + void onDirInfoValidation(boolean result); + + /** + * The operation failed. + * @param reason The reason for failure. + */ + void onFailure(int reason); + } + + /** + * Validate the Device Identity Resolution (DIR) Information of a P2P device. + * See {@link WifiP2pDirInfo} for details. + * Framework takes the {@link WifiP2pDirInfo} and derives a set of Tag values based on + * the cached Device Identity Keys (DevIK) of all paired peers saved in the device. + * If a derived Tag value matches the Tag value received in the {@link WifiP2pDirInfo}, the + * device is identified as a paired peer and returns true. + * + * <p> + * Use {@link #isWiFiDirectR2Supported()} to determine whether the device supports + * this feature. If {@link #isWiFiDirectR2Supported()} return {@code false} then + * this method will throw {@link UnsupportedOperationException}. + * <p> + * The application must have {@link android.Manifest.permission#NEARBY_WIFI_DEVICES} with + * android:usesPermissionFlags="neverForLocation". If the application does not declare + * android:usesPermissionFlags="neverForLocation", then it must also have + * {@link android.Manifest.permission#ACCESS_FINE_LOCATION}. + * + * @param c It is the channel created at {@link #initialize}. + * @param dirInfo {@link WifiP2pDirInfo} to validate. + * @param executor The executor on which callback will be invoked. + * @param callback An OutcomeReceiver callback for receiving the result via + * {@link OutcomeReceiver#onResult(Object)} indicating whether the DIR + * info of P2P device is of a paired device. {code true} for paired, + * {@code false} for not paired. + * When this API call fails due to permission issues, state machine + * is busy etc., {@link OutcomeReceiver#onError(Throwable)} is called. + */ + @RequiresPermission(allOf = { + android.Manifest.permission.NEARBY_WIFI_DEVICES, + android.Manifest.permission.ACCESS_FINE_LOCATION + }, conditional = true) + @RequiresApi(Build.VERSION_CODES.BAKLAVA) + @FlaggedApi(Flags.FLAG_WIFI_DIRECT_R2) + public void validateDirInfo(@NonNull Channel c, @NonNull WifiP2pDirInfo dirInfo, + @NonNull @CallbackExecutor Executor executor, + @NonNull OutcomeReceiver<Boolean, Exception> callback) { + if (!Environment.isSdkAtLeastB()) { + throw new UnsupportedOperationException(); + } + if (!isWiFiDirectR2Supported()) { + throw new UnsupportedOperationException(); + } + Objects.requireNonNull(c, "channel cannot be null and needs to be initialized)"); + Objects.requireNonNull(dirInfo, "dirInfo cannot be null"); + Objects.requireNonNull(executor, "executor cannot be null"); + Objects.requireNonNull(callback, "resultsCallback cannot be null"); + Bundle extras = prepareExtrasBundle(c); + + extras.putParcelable(EXTRA_PARAM_KEY_DIR_INFO, dirInfo); + c.mAsyncChannel.sendMessage(prepareMessage(VALIDATE_DIR_INFO, 0, + c.putListener(new WifiP2pDirInfoValidationListener() { + @Override + public void onDirInfoValidation(boolean result) { + Binder.clearCallingIdentity(); + executor.execute(() -> { + callback.onResult(result); + }); + } + + @Override + public void onFailure(int reason) { + Binder.clearCallingIdentity(); + executor.execute(() -> { + callback.onError(reasonCodeToException(reason)); + }); + } + }), extras, c.mContext)); + } } diff --git a/framework/java/android/net/wifi/p2p/WifiP2pPairingBootstrappingConfig.java b/framework/java/android/net/wifi/p2p/WifiP2pPairingBootstrappingConfig.java index 80d0fb14d4..4579cd2261 100644 --- a/framework/java/android/net/wifi/p2p/WifiP2pPairingBootstrappingConfig.java +++ b/framework/java/android/net/wifi/p2p/WifiP2pPairingBootstrappingConfig.java @@ -69,6 +69,12 @@ public final class WifiP2pPairingBootstrappingConfig implements Parcelable { */ public static final int PAIRING_BOOTSTRAPPING_METHOD_KEYPAD_PASSPHRASE = 1 << 4; + /** + * Pairing bootstrapping done out of band (For example: Over Bluetooth LE. + * Refer Wi-Fi Alliance Wi-Fi Direct R2 specification Section 3.9 for the details). + */ + public static final int PAIRING_BOOTSTRAPPING_METHOD_OUT_OF_BAND = 1 << 5; + /** @hide */ @IntDef(flag = true, prefix = {"PAIRING_BOOTSTRAPPING_METHOD_"}, value = { @@ -77,6 +83,7 @@ public final class WifiP2pPairingBootstrappingConfig implements Parcelable { PAIRING_BOOTSTRAPPING_METHOD_DISPLAY_PASSPHRASE, PAIRING_BOOTSTRAPPING_METHOD_KEYPAD_PINCODE, PAIRING_BOOTSTRAPPING_METHOD_KEYPAD_PASSPHRASE, + PAIRING_BOOTSTRAPPING_METHOD_OUT_OF_BAND, }) @Retention(RetentionPolicy.SOURCE) public @interface PairingBootstrappingMethod { diff --git a/framework/tests/src/android/net/wifi/p2p/WifiP2pConfigTest.java b/framework/tests/src/android/net/wifi/p2p/WifiP2pConfigTest.java index 3317d1e2ab..b11daabac5 100644 --- a/framework/tests/src/android/net/wifi/p2p/WifiP2pConfigTest.java +++ b/framework/tests/src/android/net/wifi/p2p/WifiP2pConfigTest.java @@ -462,4 +462,27 @@ public class WifiP2pConfigTest { assertNotNull(pairingBootstrappingConfig); assertEquals(expectedPairingBootstrappingConfig, pairingBootstrappingConfig); } + + /** + * Verify that a config with the request to authorize a connection request from a peer device + * can be built. + */ + @Test + public void testBuildConfigWithAuthorizeConnectionFromPeer() throws Exception { + assumeTrue(Environment.isSdkAtLeastB()); + WifiP2pPairingBootstrappingConfig expectedPairingBootstrappingConfig = + new WifiP2pPairingBootstrappingConfig(WifiP2pPairingBootstrappingConfig + .PAIRING_BOOTSTRAPPING_METHOD_OUT_OF_BAND, "1234"); + WifiP2pConfig c = new WifiP2pConfig.Builder() + .setDeviceAddress(MacAddress.fromString(DEVICE_ADDRESS)) + .setPairingBootstrappingConfig(expectedPairingBootstrappingConfig) + .setGroupOperatingFrequency(2437) + .setAuthorizeConnectionFromPeer(true) + .build(); + WifiP2pPairingBootstrappingConfig pairingBootstrappingConfig = + c.getPairingBootstrappingConfig(); + assertNotNull(pairingBootstrappingConfig); + assertEquals(expectedPairingBootstrappingConfig, pairingBootstrappingConfig); + assertTrue(c.isAuthorizeConnectionFromPeer()); + } } diff --git a/framework/tests/src/android/net/wifi/p2p/WifiP2pDirInfoTest.java b/framework/tests/src/android/net/wifi/p2p/WifiP2pDirInfoTest.java new file mode 100644 index 0000000000..f032fbeaba --- /dev/null +++ b/framework/tests/src/android/net/wifi/p2p/WifiP2pDirInfoTest.java @@ -0,0 +1,48 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.net.wifi.p2p; + +import static org.junit.Assert.assertArrayEquals; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assume.assumeTrue; + +import android.net.MacAddress; +import android.net.wifi.util.Environment; + +import androidx.test.filters.SmallTest; + +import org.junit.Test; +/** + * Unit tests for {@link WifiP2pDirInfo} + */ +@SmallTest +public final class WifiP2pDirInfoTest { + private static final String TEST_MAC_ADDRESS_STRING = "00:11:22:33:44:55"; + private static final byte[] TEST_NONCE = {10, 20, 30, 40, 50, 60, 70, 80}; + private static final byte[] TEST_DIR_TAG = {11, 22, 33, 44, 55, 66, 77, 88}; + @Test + public void testWifiP2pDirInfo() { + assumeTrue(Environment.isSdkAtLeastB()); + WifiP2pDirInfo dirInfo = new WifiP2pDirInfo( + MacAddress.fromString(TEST_MAC_ADDRESS_STRING), TEST_NONCE, TEST_DIR_TAG); + assertNotNull(dirInfo); + assertEquals(MacAddress.fromString(TEST_MAC_ADDRESS_STRING), dirInfo.getMacAddress()); + assertArrayEquals(TEST_NONCE, dirInfo.getNonce()); + assertArrayEquals(TEST_DIR_TAG, dirInfo.getDirTag()); + } +} diff --git a/framework/tests/src/android/net/wifi/p2p/WifiP2pGroupTest.java b/framework/tests/src/android/net/wifi/p2p/WifiP2pGroupTest.java index c617606891..4cb9785fa1 100644 --- a/framework/tests/src/android/net/wifi/p2p/WifiP2pGroupTest.java +++ b/framework/tests/src/android/net/wifi/p2p/WifiP2pGroupTest.java @@ -20,6 +20,7 @@ import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; +import static org.junit.Assume.assumeTrue; import android.net.InetAddresses; import android.net.MacAddress; @@ -52,6 +53,8 @@ public class WifiP2pGroupTest { private static final int FREQUENCY = 5300; private static final String CLIENT_1_DEV_ADDRESS = "aa:bb:cc:dd:ee:01"; private static final String CLIENT_2_DEV_ADDRESS = "aa:bb:cc:dd:ee:02"; + private static final byte[] GROUP_OWNER_INTERFACE_ADDRESS = + { 0x00, 0x11, 0x22, 0x33, 0x44, 0x55 }; private static final WifiP2pDevice CLIENT_1 = new WifiP2pDevice(CLIENT_1_DEV_ADDRESS); private static final WifiP2pDevice CLIENT_2 = new WifiP2pDevice(CLIENT_2_DEV_ADDRESS); private static final MacAddress CLIENT_1_INTERFACE_MAC_ADDRESS = @@ -144,4 +147,16 @@ public class WifiP2pGroupTest { assertEquals(group.toString(), fromParcel.toString()); } + + /** Verify {@link WifiP2pGroup#getGroupOwnerBssid()} */ + @Test + public void testGetGroupOwnerBssid() throws Exception { + assumeTrue(Environment.isSdkAtLeastB()); + WifiP2pGroup group = new WifiP2pGroup(); + group.setIsGroupOwner(true); + group.interfaceAddress = GROUP_OWNER_INTERFACE_ADDRESS; + group.setSecurityType(WifiP2pGroup.SECURITY_TYPE_WPA3_SAE); + assertEquals(MacAddress.fromBytes(GROUP_OWNER_INTERFACE_ADDRESS), + group.getGroupOwnerBssid()); + } } diff --git a/service/java/com/android/server/wifi/p2p/WifiP2pServiceImpl.java b/service/java/com/android/server/wifi/p2p/WifiP2pServiceImpl.java index eb082213ce..19456d644d 100644 --- a/service/java/com/android/server/wifi/p2p/WifiP2pServiceImpl.java +++ b/service/java/com/android/server/wifi/p2p/WifiP2pServiceImpl.java @@ -664,6 +664,8 @@ public class WifiP2pServiceImpl extends IWifiP2pManager.Stub { case WifiP2pManager.REMOVE_EXTERNAL_APPROVER: case WifiP2pManager.SET_CONNECTION_REQUEST_RESULT: case WifiP2pManager.SET_VENDOR_ELEMENTS: + case WifiP2pManager.GET_DIR_INFO: + case WifiP2pManager.VALIDATE_DIR_INFO: mP2pStateMachine.sendMessage(Message.obtain(msg)); break; default: @@ -1917,6 +1919,10 @@ public class WifiP2pServiceImpl extends IWifiP2pManager.Stub { return "WifiP2pManager.SET_VENDOR_ELEMENTS"; case P2P_REJECTION_RESUME_AFTER_DELAY: return "P2P_REJECTION_RESUME_AFTER_DELAY"; + case WifiP2pManager.GET_DIR_INFO: + return "WifiP2pManager.GET_DIR_INFO"; + case WifiP2pManager.VALIDATE_DIR_INFO: + return "WifiP2pManager.VALIDATE_DIR_INFO"; case RunnerState.STATE_ENTER_CMD: return "Enter"; case RunnerState.STATE_EXIT_CMD: @@ -3562,7 +3568,7 @@ public class WifiP2pServiceImpl extends IWifiP2pManager.Stub { case SET_MIRACAST_MODE: mWifiNative.setMiracastMode(message.arg1); break; - case WifiP2pManager.START_LISTEN: + case WifiP2pManager.START_LISTEN: { String packageName = getCallingPkgName(message.sendingUid, message.replyTo); if (packageName == null) { replyToMessage(message, WifiP2pManager.START_LISTEN_FAILED); @@ -3579,10 +3585,10 @@ public class WifiP2pServiceImpl extends IWifiP2pManager.Stub { .getBundle(WifiP2pManager.EXTRA_PARAM_KEY_BUNDLE); WifiP2pExtListenParams extListenParams = SdkLevel.isAtLeastV() && (listenType == WifiP2pManager.WIFI_P2P_EXT_LISTEN_WITH_PARAMS) - ? extras.getParcelable( - WifiP2pManager.EXTRA_PARAM_KEY_EXT_LISTEN_PARAMS, - WifiP2pExtListenParams.class) - : null; + ? extras.getParcelable( + WifiP2pManager.EXTRA_PARAM_KEY_EXT_LISTEN_PARAMS, + WifiP2pExtListenParams.class) + : null; boolean hasPermission; if (isPlatformOrTargetSdkLessThanT(packageName, uid)) { hasPermission = mWifiPermissionsUtil.checkCanAccessWifiDirect( @@ -3613,6 +3619,7 @@ public class WifiP2pServiceImpl extends IWifiP2pManager.Stub { replyToMessage(message, WifiP2pManager.START_LISTEN_FAILED); } break; + } case WifiP2pManager.STOP_LISTEN: mLastCallerInfoManager.put(WifiManager.API_P2P_STOP_LISTENING, Process.myTid(), message.sendingUid, 0, @@ -3673,6 +3680,37 @@ public class WifiP2pServiceImpl extends IWifiP2pManager.Stub { } updateP2pChannels(); break; + case WifiP2pManager.GET_DIR_INFO: { + String packageName = getCallingPkgName(message.sendingUid, message.replyTo); + if (packageName == null) { + replyToMessage(message, WifiP2pManager.GET_DIR_INFO_FAILED); + break; + } + if (!Environment.isSdkAtLeastB() + || !checkNearbyDevicesPermission(message, "GET_DIR_INFO")) { + replyToMessage(message, WifiP2pManager.GET_DIR_INFO_FAILED); + break; + } + // TODO implementation + replyToMessage(message, WifiP2pManager.RESPONSE_GET_DIR_INFO, null); + break; + } + case WifiP2pManager.VALIDATE_DIR_INFO: { + String packageName = getCallingPkgName(message.sendingUid, message.replyTo); + if (packageName == null) { + replyToMessage(message, WifiP2pManager.VALIDATE_DIR_INFO_FAILED); + break; + } + if (!Environment.isSdkAtLeastB() + || !checkNearbyDevicesPermission(message, + "VALIDATE_DIR_INFO")) { + replyToMessage(message, WifiP2pManager.VALIDATE_DIR_INFO_FAILED); + break; + } + // TODO implementation + replyToMessage(message, WifiP2pManager.RESPONSE_VALIDATE_DIR_INFO, 0); + break; + } default: return NOT_HANDLED; } diff --git a/service/tests/wifitests/src/com/android/server/wifi/p2p/WifiP2pServiceImplTest.java b/service/tests/wifitests/src/com/android/server/wifi/p2p/WifiP2pServiceImplTest.java index 6b2b59eb8a..1298be08b0 100644 --- a/service/tests/wifitests/src/com/android/server/wifi/p2p/WifiP2pServiceImplTest.java +++ b/service/tests/wifitests/src/com/android/server/wifi/p2p/WifiP2pServiceImplTest.java @@ -109,6 +109,7 @@ import android.net.wifi.p2p.WifiP2pProvDiscEvent; import android.net.wifi.p2p.WifiP2pWfdInfo; import android.net.wifi.p2p.nsd.WifiP2pServiceInfo; import android.net.wifi.p2p.nsd.WifiP2pServiceRequest; +import android.net.wifi.util.Environment; import android.os.Binder; import android.os.Build; import android.os.Bundle; @@ -8636,4 +8637,48 @@ public class WifiP2pServiceImplTest extends WifiBaseTest { verify(mWifiNative).teardownInterface(); verify(mWifiMonitor).stopMonitoring(anyString()); } + + /** + * Verify {@link WifiP2pManager#GET_DIR_INFO} message. + */ + @Test + public void testGetDirInfo() throws Exception { + assumeTrue(Environment.isSdkAtLeastB()); + forceP2pEnabled(mClient1); + when(mWifiPermissionsUtil.checkNearbyDevicesPermission(any(), anyBoolean(), any())) + .thenReturn(false); + sendSimpleMsg(mClientMessenger, WifiP2pManager.GET_DIR_INFO); + assertTrue(mClientHandler.hasMessages(WifiP2pManager.GET_DIR_INFO_FAILED)); + + when(mWifiPermissionsUtil.checkNearbyDevicesPermission(any(), anyBoolean(), any())) + .thenReturn(true); + sendSimpleMsg(mClientMessenger, WifiP2pManager.GET_DIR_INFO); + + verify(mClientHandler, times(2)).sendMessage(mMessageCaptor.capture()); + List<Message> messages = mMessageCaptor.getAllValues(); + assertEquals(WifiP2pManager.GET_DIR_INFO_FAILED, messages.get(0).what); + assertEquals(WifiP2pManager.RESPONSE_GET_DIR_INFO, messages.get(1).what); + } + + /** + * Verify {@link WifiP2pManager#VALIDATE_DIR_INFO} message. + */ + @Test + public void testValidateDirInfo() throws Exception { + assumeTrue(Environment.isSdkAtLeastB()); + forceP2pEnabled(mClient1); + when(mWifiPermissionsUtil.checkNearbyDevicesPermission(any(), anyBoolean(), any())) + .thenReturn(false); + sendSimpleMsg(mClientMessenger, WifiP2pManager.VALIDATE_DIR_INFO); + assertTrue(mClientHandler.hasMessages(WifiP2pManager.VALIDATE_DIR_INFO_FAILED)); + + when(mWifiPermissionsUtil.checkNearbyDevicesPermission(any(), anyBoolean(), any())) + .thenReturn(true); + sendSimpleMsg(mClientMessenger, WifiP2pManager.VALIDATE_DIR_INFO); + + verify(mClientHandler, times(2)).sendMessage(mMessageCaptor.capture()); + List<Message> messages = mMessageCaptor.getAllValues(); + assertEquals(WifiP2pManager.VALIDATE_DIR_INFO_FAILED, messages.get(0).what); + assertEquals(WifiP2pManager.RESPONSE_VALIDATE_DIR_INFO, messages.get(1).what); + } } |