1 /* 2 * Copyright (C) 2011 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package android.net.wifi.p2p; 18 19 import android.annotation.IntDef; 20 import android.annotation.IntRange; 21 import android.annotation.NonNull; 22 import android.annotation.Nullable; 23 import android.compat.annotation.UnsupportedAppUsage; 24 import android.net.MacAddress; 25 import android.net.wifi.WpsInfo; 26 import android.os.Build; 27 import android.os.Parcel; 28 import android.os.Parcelable; 29 import android.text.TextUtils; 30 31 import java.lang.annotation.Retention; 32 import java.lang.annotation.RetentionPolicy; 33 import java.nio.charset.StandardCharsets; 34 import java.util.regex.PatternSyntaxException; 35 36 /** 37 * A class representing a Wi-Fi P2p configuration for setting up a connection 38 * 39 * {@see WifiP2pManager} 40 */ 41 public class WifiP2pConfig implements Parcelable { 42 43 /** 44 * The device MAC address uniquely identifies a Wi-Fi p2p device 45 */ 46 public String deviceAddress = ""; 47 48 /** 49 * Wi-Fi Protected Setup information 50 */ 51 public WpsInfo wps; 52 53 /** Get the network name of this P2P configuration, or null if unset. */ 54 @Nullable getNetworkName()55 public String getNetworkName() { 56 return networkName; 57 } 58 59 /** @hide */ 60 public String networkName = ""; 61 62 /** Get the passphrase of this P2P configuration, or null if unset. */ 63 @Nullable getPassphrase()64 public String getPassphrase() { 65 return passphrase; 66 } 67 68 /** @hide */ 69 public String passphrase = ""; 70 71 /** 72 * Get the required band for the group owner. 73 * The result will be one of the following: 74 * {@link #GROUP_OWNER_BAND_AUTO}, 75 * {@link #GROUP_OWNER_BAND_2GHZ}, 76 * {@link #GROUP_OWNER_BAND_5GHZ} 77 */ 78 @GroupOperatingBandType getGroupOwnerBand()79 public int getGroupOwnerBand() { 80 return groupOwnerBand; 81 } 82 83 /** @hide */ 84 @GroupOperatingBandType 85 public int groupOwnerBand = GROUP_OWNER_BAND_AUTO; 86 87 /** @hide */ 88 @IntDef(flag = false, prefix = { "GROUP_OWNER_BAND_" }, value = { 89 GROUP_OWNER_BAND_AUTO, 90 GROUP_OWNER_BAND_2GHZ, 91 GROUP_OWNER_BAND_5GHZ 92 }) 93 @Retention(RetentionPolicy.SOURCE) 94 public @interface GroupOperatingBandType {} 95 96 /** 97 * Allow the system to pick the operating frequency from all supported bands. 98 */ 99 public static final int GROUP_OWNER_BAND_AUTO = 0; 100 /** 101 * Allow the system to pick the operating frequency from the 2.4 GHz band. 102 */ 103 public static final int GROUP_OWNER_BAND_2GHZ = 1; 104 /** 105 * Allow the system to pick the operating frequency from the 5 GHz band. 106 */ 107 public static final int GROUP_OWNER_BAND_5GHZ = 2; 108 109 /** 110 * The least inclination to be a group owner, to be filled in the field 111 * {@link #groupOwnerIntent}. 112 */ 113 public static final int GROUP_OWNER_INTENT_MIN = 0; 114 115 /** 116 * The most inclination to be a group owner, to be filled in the field 117 * {@link #groupOwnerIntent}. 118 */ 119 public static final int GROUP_OWNER_INTENT_MAX = 15; 120 121 /** 122 * The system can choose an appropriate owner intent value, to be filled in the field 123 * {@link #groupOwnerIntent}. 124 */ 125 public static final int GROUP_OWNER_INTENT_AUTO = -1; 126 127 /** 128 * This is an integer value between {@link #GROUP_OWNER_INTENT_MIN} and 129 * {@link #GROUP_OWNER_INTENT_MAX} where 130 * {@link #GROUP_OWNER_INTENT_MIN} indicates the least inclination to be a group owner and 131 * {@link #GROUP_OWNER_INTENT_MAX} indicates the highest inclination to be a group owner. 132 * 133 * A value of {@link #GROUP_OWNER_INTENT_AUTO} indicates the system can choose an appropriate 134 * value. 135 * 136 * By default this field is set to {@link #GROUP_OWNER_INTENT_AUTO}. 137 */ 138 @IntRange(from = 0, to = 15) 139 public int groupOwnerIntent = GROUP_OWNER_INTENT_AUTO; 140 141 /** @hide */ 142 @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) 143 public int netId = WifiP2pGroup.NETWORK_ID_PERSISTENT; 144 145 /** 146 * Get the network ID of this P2P configuration. 147 * @return either a non-negative network ID, or one of 148 * {@link WifiP2pGroup#NETWORK_ID_PERSISTENT} or {@link WifiP2pGroup#NETWORK_ID_TEMPORARY}. 149 */ getNetworkId()150 public int getNetworkId() { 151 return netId; 152 } 153 WifiP2pConfig()154 public WifiP2pConfig() { 155 //set defaults 156 wps = new WpsInfo(); 157 wps.setup = WpsInfo.PBC; 158 } 159 160 /** @hide */ invalidate()161 public void invalidate() { 162 deviceAddress = ""; 163 } 164 165 /** P2P-GO-NEG-REQUEST 42:fc:89:a8:96:09 dev_passwd_id=4 {@hide}*/ 166 @UnsupportedAppUsage WifiP2pConfig(String supplicantEvent)167 public WifiP2pConfig(String supplicantEvent) throws IllegalArgumentException { 168 String[] tokens = supplicantEvent.split(" "); 169 170 if (tokens.length < 2 || !tokens[0].equals("P2P-GO-NEG-REQUEST")) { 171 throw new IllegalArgumentException("Malformed supplicant event"); 172 } 173 174 deviceAddress = tokens[1]; 175 wps = new WpsInfo(); 176 177 if (tokens.length > 2) { 178 String[] nameVal = tokens[2].split("="); 179 int devPasswdId; 180 try { 181 devPasswdId = Integer.parseInt(nameVal[1]); 182 } catch (NumberFormatException e) { 183 devPasswdId = 0; 184 } 185 //Based on definitions in wps/wps_defs.h 186 switch (devPasswdId) { 187 //DEV_PW_USER_SPECIFIED = 0x0001, 188 case 0x01: 189 wps.setup = WpsInfo.DISPLAY; 190 break; 191 //DEV_PW_PUSHBUTTON = 0x0004, 192 case 0x04: 193 wps.setup = WpsInfo.PBC; 194 break; 195 //DEV_PW_REGISTRAR_SPECIFIED = 0x0005 196 case 0x05: 197 wps.setup = WpsInfo.KEYPAD; 198 break; 199 default: 200 wps.setup = WpsInfo.PBC; 201 break; 202 } 203 } 204 } 205 toString()206 public String toString() { 207 StringBuffer sbuf = new StringBuffer(); 208 sbuf.append("\n address: ").append(deviceAddress); 209 sbuf.append("\n wps: ").append(wps); 210 sbuf.append("\n groupOwnerIntent: ").append(groupOwnerIntent); 211 sbuf.append("\n persist: ").append(netId); 212 sbuf.append("\n networkName: ").append(networkName); 213 sbuf.append("\n passphrase: ").append( 214 TextUtils.isEmpty(passphrase) ? "<empty>" : "<non-empty>"); 215 sbuf.append("\n groupOwnerBand: ").append(groupOwnerBand); 216 return sbuf.toString(); 217 } 218 219 /** Implement the Parcelable interface */ describeContents()220 public int describeContents() { 221 return 0; 222 } 223 224 /** copy constructor */ WifiP2pConfig(WifiP2pConfig source)225 public WifiP2pConfig(WifiP2pConfig source) { 226 if (source != null) { 227 deviceAddress = source.deviceAddress; 228 wps = new WpsInfo(source.wps); 229 groupOwnerIntent = source.groupOwnerIntent; 230 netId = source.netId; 231 networkName = source.networkName; 232 passphrase = source.passphrase; 233 groupOwnerBand = source.groupOwnerBand; 234 } 235 } 236 237 /** Implement the Parcelable interface */ writeToParcel(Parcel dest, int flags)238 public void writeToParcel(Parcel dest, int flags) { 239 dest.writeString(deviceAddress); 240 dest.writeParcelable(wps, flags); 241 dest.writeInt(groupOwnerIntent); 242 dest.writeInt(netId); 243 dest.writeString(networkName); 244 dest.writeString(passphrase); 245 dest.writeInt(groupOwnerBand); 246 } 247 248 /** Implement the Parcelable interface */ 249 public static final @android.annotation.NonNull Creator<WifiP2pConfig> CREATOR = 250 new Creator<WifiP2pConfig>() { 251 public WifiP2pConfig createFromParcel(Parcel in) { 252 WifiP2pConfig config = new WifiP2pConfig(); 253 config.deviceAddress = in.readString(); 254 config.wps = (WpsInfo) in.readParcelable(null); 255 config.groupOwnerIntent = in.readInt(); 256 config.netId = in.readInt(); 257 config.networkName = in.readString(); 258 config.passphrase = in.readString(); 259 config.groupOwnerBand = in.readInt(); 260 return config; 261 } 262 263 public WifiP2pConfig[] newArray(int size) { 264 return new WifiP2pConfig[size]; 265 } 266 }; 267 268 /** 269 * Builder used to build {@link WifiP2pConfig} objects for 270 * creating or joining a group. 271 */ 272 public static final class Builder { 273 274 private static final MacAddress MAC_ANY_ADDRESS = 275 MacAddress.fromString("02:00:00:00:00:00"); 276 /** 277 * Maximum number of bytes allowed for a SSID. 278 */ 279 private static final int MAX_SSID_BYTES = 32; 280 281 private MacAddress mDeviceAddress = MAC_ANY_ADDRESS; 282 private String mNetworkName = ""; 283 private String mPassphrase = ""; 284 private int mGroupOperatingBand = GROUP_OWNER_BAND_AUTO; 285 private int mGroupOperatingFrequency = GROUP_OWNER_BAND_AUTO; 286 private int mNetId = WifiP2pGroup.NETWORK_ID_TEMPORARY; 287 288 /** 289 * Specify the peer's MAC address. If not set, the device will 290 * try to find a peer whose SSID matches the network name as 291 * specified by {@link #setNetworkName(String)}. Specifying null will 292 * reset the peer's MAC address to "02:00:00:00:00:00". 293 * <p> 294 * Optional. "02:00:00:00:00:00" by default. 295 * 296 * @param deviceAddress the peer's MAC address. 297 * @return The builder to facilitate chaining 298 * {@code builder.setXXX(..).setXXX(..)}. 299 */ setDeviceAddress(@ullable MacAddress deviceAddress)300 public @NonNull Builder setDeviceAddress(@Nullable MacAddress deviceAddress) { 301 if (deviceAddress == null) { 302 mDeviceAddress = MAC_ANY_ADDRESS; 303 } else { 304 mDeviceAddress = deviceAddress; 305 } 306 return this; 307 } 308 309 /** 310 * Specify the network name, a.k.a. group name, 311 * for creating or joining a group. 312 * <p> 313 * A network name shall begin with "DIRECT-xy". x and y are selected 314 * from the following character set: upper case letters, lower case 315 * letters and numbers. Any byte values allowed for an SSID according to 316 * IEEE802.11-2012 [1] may be included after the string "DIRECT-xy" 317 * (including none). 318 * <p> 319 * Must be called - an empty network name or an network name 320 * not conforming to the P2P Group ID naming rule is not valid. 321 * 322 * @param networkName network name of a group. 323 * @return The builder to facilitate chaining 324 * {@code builder.setXXX(..).setXXX(..)}. 325 */ setNetworkName(@onNull String networkName)326 public @NonNull Builder setNetworkName(@NonNull String networkName) { 327 if (TextUtils.isEmpty(networkName)) { 328 throw new IllegalArgumentException( 329 "network name must be non-empty."); 330 } 331 if (networkName.getBytes(StandardCharsets.UTF_8).length > MAX_SSID_BYTES) { 332 throw new IllegalArgumentException( 333 "network name exceeds " + MAX_SSID_BYTES + " bytes."); 334 } 335 try { 336 if (!networkName.matches("^DIRECT-[a-zA-Z0-9]{2}.*")) { 337 throw new IllegalArgumentException( 338 "network name must starts with the prefix DIRECT-xy."); 339 } 340 } catch (PatternSyntaxException e) { 341 // can never happen (fixed pattern) 342 } 343 mNetworkName = networkName; 344 return this; 345 } 346 347 /** 348 * Specify the passphrase for creating or joining a group. 349 * <p> 350 * The passphrase must be an ASCII string whose length is between 8 351 * and 63. 352 * <p> 353 * Must be called - an empty passphrase is not valid. 354 * 355 * @param passphrase the passphrase of a group. 356 * @return The builder to facilitate chaining 357 * {@code builder.setXXX(..).setXXX(..)}. 358 */ setPassphrase(@onNull String passphrase)359 public @NonNull Builder setPassphrase(@NonNull String passphrase) { 360 if (TextUtils.isEmpty(passphrase)) { 361 throw new IllegalArgumentException( 362 "passphrase must be non-empty."); 363 } 364 if (passphrase.length() < 8 || passphrase.length() > 63) { 365 throw new IllegalArgumentException( 366 "The length of a passphrase must be between 8 and 63."); 367 } 368 mPassphrase = passphrase; 369 return this; 370 } 371 372 /** 373 * Specify the band to use for creating the group or joining the group. The band should 374 * be {@link #GROUP_OWNER_BAND_2GHZ}, {@link #GROUP_OWNER_BAND_5GHZ} or 375 * {@link #GROUP_OWNER_BAND_AUTO}. 376 * <p> 377 * When creating a group as Group Owner using {@link 378 * WifiP2pManager#createGroup(WifiP2pManager.Channel, 379 * WifiP2pConfig, WifiP2pManager.ActionListener)}, 380 * specifying {@link #GROUP_OWNER_BAND_AUTO} allows the system to pick the operating 381 * frequency from all supported bands. 382 * Specifying {@link #GROUP_OWNER_BAND_2GHZ} or {@link #GROUP_OWNER_BAND_5GHZ} 383 * only allows the system to pick the operating frequency in the specified band. 384 * If the Group Owner cannot create a group in the specified band, the operation will fail. 385 * <p> 386 * When joining a group as Group Client using {@link 387 * WifiP2pManager#connect(WifiP2pManager.Channel, WifiP2pConfig, 388 * WifiP2pManager.ActionListener)}, 389 * specifying {@link #GROUP_OWNER_BAND_AUTO} allows the system to scan all supported 390 * frequencies to find the desired group. Specifying {@link #GROUP_OWNER_BAND_2GHZ} or 391 * {@link #GROUP_OWNER_BAND_5GHZ} only allows the system to scan the specified band. 392 * <p> 393 * {@link #setGroupOperatingBand(int)} and {@link #setGroupOperatingFrequency(int)} are 394 * mutually exclusive. Setting operating band and frequency both is invalid. 395 * <p> 396 * Optional. {@link #GROUP_OWNER_BAND_AUTO} by default. 397 * 398 * @param band the operating band of the group. 399 * This should be one of {@link #GROUP_OWNER_BAND_AUTO}, 400 * {@link #GROUP_OWNER_BAND_2GHZ}, {@link #GROUP_OWNER_BAND_5GHZ}. 401 * @return The builder to facilitate chaining 402 * {@code builder.setXXX(..).setXXX(..)}. 403 */ setGroupOperatingBand(@roupOperatingBandType int band)404 public @NonNull Builder setGroupOperatingBand(@GroupOperatingBandType int band) { 405 switch (band) { 406 case GROUP_OWNER_BAND_AUTO: 407 case GROUP_OWNER_BAND_2GHZ: 408 case GROUP_OWNER_BAND_5GHZ: 409 mGroupOperatingBand = band; 410 break; 411 default: 412 throw new IllegalArgumentException( 413 "Invalid constant for the group operating band!"); 414 } 415 return this; 416 } 417 418 /** 419 * Specify the frequency, in MHz, to use for creating the group or joining the group. 420 * <p> 421 * When creating a group as Group Owner using {@link WifiP2pManager#createGroup( 422 * WifiP2pManager.Channel, WifiP2pConfig, WifiP2pManager.ActionListener)}, 423 * specifying a frequency only allows the system to pick the specified frequency. 424 * If the Group Owner cannot create a group at the specified frequency, 425 * the operation will fail. 426 * When not specifying a frequency, it allows the system to pick operating frequency 427 * from all supported bands. 428 * <p> 429 * When joining a group as Group Client using {@link WifiP2pManager#connect( 430 * WifiP2pManager.Channel, WifiP2pConfig, WifiP2pManager.ActionListener)}, 431 * specifying a frequency only allows the system to scan the specified frequency. 432 * If the frequency is not supported or invalid, the operation will fail. 433 * When not specifying a frequency, it allows the system to scan all supported 434 * frequencies to find the desired group. 435 * <p> 436 * {@link #setGroupOperatingBand(int)} and {@link #setGroupOperatingFrequency(int)} are 437 * mutually exclusive. Setting operating band and frequency both is invalid. 438 * <p> 439 * Optional. 0 by default. 440 * 441 * @param frequency the operating frequency of the group. 442 * @return The builder to facilitate chaining 443 * {@code builder.setXXX(..).setXXX(..)}. 444 */ setGroupOperatingFrequency(int frequency)445 public @NonNull Builder setGroupOperatingFrequency(int frequency) { 446 if (frequency < 0) { 447 throw new IllegalArgumentException( 448 "Invalid group operating frequency!"); 449 } 450 mGroupOperatingFrequency = frequency; 451 return this; 452 } 453 454 /** 455 * Specify that the group configuration be persisted (i.e. saved). 456 * By default the group configuration will not be saved. 457 * <p> 458 * Optional. false by default. 459 * 460 * @param persistent is this group persistent group. 461 * @return The builder to facilitate chaining 462 * {@code builder.setXXX(..).setXXX(..)}. 463 */ enablePersistentMode(boolean persistent)464 public @NonNull Builder enablePersistentMode(boolean persistent) { 465 if (persistent) { 466 mNetId = WifiP2pGroup.NETWORK_ID_PERSISTENT; 467 } else { 468 mNetId = WifiP2pGroup.NETWORK_ID_TEMPORARY; 469 } 470 return this; 471 } 472 473 /** 474 * Build {@link WifiP2pConfig} given the current requests made on the builder. 475 * @return {@link WifiP2pConfig} constructed based on builder method calls. 476 */ build()477 public @NonNull WifiP2pConfig build() { 478 if (TextUtils.isEmpty(mNetworkName)) { 479 throw new IllegalStateException( 480 "network name must be non-empty."); 481 } 482 if (TextUtils.isEmpty(mPassphrase)) { 483 throw new IllegalStateException( 484 "passphrase must be non-empty."); 485 } 486 487 if (mGroupOperatingFrequency > 0 && mGroupOperatingBand > 0) { 488 throw new IllegalStateException( 489 "Preferred frequency and band are mutually exclusive."); 490 } 491 492 WifiP2pConfig config = new WifiP2pConfig(); 493 config.deviceAddress = mDeviceAddress.toString(); 494 config.networkName = mNetworkName; 495 config.passphrase = mPassphrase; 496 config.groupOwnerBand = GROUP_OWNER_BAND_AUTO; 497 if (mGroupOperatingFrequency > 0) { 498 config.groupOwnerBand = mGroupOperatingFrequency; 499 } else if (mGroupOperatingBand > 0) { 500 config.groupOwnerBand = mGroupOperatingBand; 501 } 502 config.netId = mNetId; 503 return config; 504 } 505 } 506 } 507