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 * The WifiP2pConfig can be constructed for two use-cases: 273 * <ul> 274 * <li>SSID + Passphrase are known: use {@link #setNetworkName(String)} and 275 * {@link #setPassphrase(String)}.</li> 276 * <li>SSID or Passphrase is unknown, in such a case the MAC address must be known and 277 * specified using {@link #setDeviceAddress(MacAddress)}.</li> 278 * </ul> 279 */ 280 public static final class Builder { 281 282 private static final MacAddress MAC_ANY_ADDRESS = 283 MacAddress.fromString("02:00:00:00:00:00"); 284 /** 285 * Maximum number of bytes allowed for a SSID. 286 */ 287 private static final int MAX_SSID_BYTES = 32; 288 289 private MacAddress mDeviceAddress = MAC_ANY_ADDRESS; 290 private String mNetworkName = ""; 291 private String mPassphrase = ""; 292 private int mGroupOperatingBand = GROUP_OWNER_BAND_AUTO; 293 private int mGroupOperatingFrequency = GROUP_OWNER_BAND_AUTO; 294 private int mNetId = WifiP2pGroup.NETWORK_ID_TEMPORARY; 295 296 /** 297 * Specify the peer's MAC address. If not set, the device will 298 * try to find a peer whose SSID matches the network name as 299 * specified by {@link #setNetworkName(String)}. Specifying null will 300 * reset the peer's MAC address to "02:00:00:00:00:00". 301 * <p> 302 * Optional. "02:00:00:00:00:00" by default. 303 * 304 * <p> If the network name is not set, the peer's MAC address is mandatory. 305 * 306 * @param deviceAddress the peer's MAC address. 307 * @return The builder to facilitate chaining 308 * {@code builder.setXXX(..).setXXX(..)}. 309 */ setDeviceAddress(@ullable MacAddress deviceAddress)310 public @NonNull Builder setDeviceAddress(@Nullable MacAddress deviceAddress) { 311 if (deviceAddress == null) { 312 mDeviceAddress = MAC_ANY_ADDRESS; 313 } else { 314 mDeviceAddress = deviceAddress; 315 } 316 return this; 317 } 318 319 /** 320 * Specify the network name, a.k.a. group name, 321 * for creating or joining a group. 322 * <p> 323 * A network name shall begin with "DIRECT-xy". x and y are selected 324 * from the following character set: upper case letters, lower case 325 * letters and numbers. Any byte values allowed for an SSID according to 326 * IEEE802.11-2012 [1] may be included after the string "DIRECT-xy" 327 * (including none). 328 * <p> 329 * Must be called - an empty network name or an network name 330 * not conforming to the P2P Group ID naming rule is not valid. 331 * 332 * @param networkName network name of a group. 333 * @return The builder to facilitate chaining 334 * {@code builder.setXXX(..).setXXX(..)}. 335 */ setNetworkName(@onNull String networkName)336 public @NonNull Builder setNetworkName(@NonNull String networkName) { 337 if (TextUtils.isEmpty(networkName)) { 338 throw new IllegalArgumentException( 339 "network name must be non-empty."); 340 } 341 if (networkName.getBytes(StandardCharsets.UTF_8).length > MAX_SSID_BYTES) { 342 throw new IllegalArgumentException( 343 "network name exceeds " + MAX_SSID_BYTES + " bytes."); 344 } 345 try { 346 if (!networkName.matches("^DIRECT-[a-zA-Z0-9]{2}.*")) { 347 throw new IllegalArgumentException( 348 "network name must starts with the prefix DIRECT-xy."); 349 } 350 } catch (PatternSyntaxException e) { 351 // can never happen (fixed pattern) 352 } 353 mNetworkName = networkName; 354 return this; 355 } 356 357 /** 358 * Specify the passphrase for creating or joining a group. 359 * <p> 360 * The passphrase must be an ASCII string whose length is between 8 361 * and 63. 362 * <p> 363 * Must be called - an empty passphrase is not valid. 364 * 365 * @param passphrase the passphrase of a group. 366 * @return The builder to facilitate chaining 367 * {@code builder.setXXX(..).setXXX(..)}. 368 */ setPassphrase(@onNull String passphrase)369 public @NonNull Builder setPassphrase(@NonNull String passphrase) { 370 if (TextUtils.isEmpty(passphrase)) { 371 throw new IllegalArgumentException( 372 "passphrase must be non-empty."); 373 } 374 if (passphrase.length() < 8 || passphrase.length() > 63) { 375 throw new IllegalArgumentException( 376 "The length of a passphrase must be between 8 and 63."); 377 } 378 mPassphrase = passphrase; 379 return this; 380 } 381 382 /** 383 * Specify the band to use for creating the group or joining the group. The band should 384 * be {@link #GROUP_OWNER_BAND_2GHZ}, {@link #GROUP_OWNER_BAND_5GHZ} or 385 * {@link #GROUP_OWNER_BAND_AUTO}. 386 * <p> 387 * When creating a group as Group Owner using {@link 388 * WifiP2pManager#createGroup(WifiP2pManager.Channel, 389 * WifiP2pConfig, WifiP2pManager.ActionListener)}, 390 * specifying {@link #GROUP_OWNER_BAND_AUTO} allows the system to pick the operating 391 * frequency from all supported bands. 392 * Specifying {@link #GROUP_OWNER_BAND_2GHZ} or {@link #GROUP_OWNER_BAND_5GHZ} 393 * only allows the system to pick the operating frequency in the specified band. 394 * If the Group Owner cannot create a group in the specified band, the operation will fail. 395 * <p> 396 * When joining a group as Group Client using {@link 397 * WifiP2pManager#connect(WifiP2pManager.Channel, WifiP2pConfig, 398 * WifiP2pManager.ActionListener)}, 399 * specifying {@link #GROUP_OWNER_BAND_AUTO} allows the system to scan all supported 400 * frequencies to find the desired group. Specifying {@link #GROUP_OWNER_BAND_2GHZ} or 401 * {@link #GROUP_OWNER_BAND_5GHZ} only allows the system to scan the specified band. 402 * <p> 403 * {@link #setGroupOperatingBand(int)} and {@link #setGroupOperatingFrequency(int)} are 404 * mutually exclusive. Setting operating band and frequency both is invalid. 405 * <p> 406 * Optional. {@link #GROUP_OWNER_BAND_AUTO} by default. 407 * 408 * @param band the operating band of the group. 409 * This should be one of {@link #GROUP_OWNER_BAND_AUTO}, 410 * {@link #GROUP_OWNER_BAND_2GHZ}, {@link #GROUP_OWNER_BAND_5GHZ}. 411 * @return The builder to facilitate chaining 412 * {@code builder.setXXX(..).setXXX(..)}. 413 */ setGroupOperatingBand(@roupOperatingBandType int band)414 public @NonNull Builder setGroupOperatingBand(@GroupOperatingBandType int band) { 415 switch (band) { 416 case GROUP_OWNER_BAND_AUTO: 417 case GROUP_OWNER_BAND_2GHZ: 418 case GROUP_OWNER_BAND_5GHZ: 419 mGroupOperatingBand = band; 420 break; 421 default: 422 throw new IllegalArgumentException( 423 "Invalid constant for the group operating band!"); 424 } 425 return this; 426 } 427 428 /** 429 * Specify the frequency, in MHz, to use for creating the group or joining the group. 430 * <p> 431 * When creating a group as Group Owner using {@link WifiP2pManager#createGroup( 432 * WifiP2pManager.Channel, WifiP2pConfig, WifiP2pManager.ActionListener)}, 433 * specifying a frequency only allows the system to pick the specified frequency. 434 * If the Group Owner cannot create a group at the specified frequency, 435 * the operation will fail. 436 * When not specifying a frequency, it allows the system to pick operating frequency 437 * from all supported bands. 438 * <p> 439 * When joining a group as Group Client using {@link WifiP2pManager#connect( 440 * WifiP2pManager.Channel, WifiP2pConfig, WifiP2pManager.ActionListener)}, 441 * specifying a frequency only allows the system to scan the specified frequency. 442 * If the frequency is not supported or invalid, the operation will fail. 443 * When not specifying a frequency, it allows the system to scan all supported 444 * frequencies to find the desired group. 445 * <p> 446 * {@link #setGroupOperatingBand(int)} and {@link #setGroupOperatingFrequency(int)} are 447 * mutually exclusive. Setting operating band and frequency both is invalid. 448 * <p> 449 * Optional. 0 by default. 450 * 451 * @param frequency the operating frequency of the group. 452 * @return The builder to facilitate chaining 453 * {@code builder.setXXX(..).setXXX(..)}. 454 */ setGroupOperatingFrequency(int frequency)455 public @NonNull Builder setGroupOperatingFrequency(int frequency) { 456 if (frequency < 0) { 457 throw new IllegalArgumentException( 458 "Invalid group operating frequency!"); 459 } 460 mGroupOperatingFrequency = frequency; 461 return this; 462 } 463 464 /** 465 * Specify that the group configuration be persisted (i.e. saved). 466 * By default the group configuration will not be saved. 467 * <p> 468 * Optional. false by default. 469 * 470 * @param persistent is this group persistent group. 471 * @return The builder to facilitate chaining 472 * {@code builder.setXXX(..).setXXX(..)}. 473 */ enablePersistentMode(boolean persistent)474 public @NonNull Builder enablePersistentMode(boolean persistent) { 475 if (persistent) { 476 mNetId = WifiP2pGroup.NETWORK_ID_PERSISTENT; 477 } else { 478 mNetId = WifiP2pGroup.NETWORK_ID_TEMPORARY; 479 } 480 return this; 481 } 482 483 /** 484 * Build {@link WifiP2pConfig} given the current requests made on the builder. 485 * @return {@link WifiP2pConfig} constructed based on builder method calls. 486 */ build()487 public @NonNull WifiP2pConfig build() { 488 if ((TextUtils.isEmpty(mNetworkName) && !TextUtils.isEmpty(mPassphrase)) 489 || (!TextUtils.isEmpty(mNetworkName) && TextUtils.isEmpty(mPassphrase))) { 490 throw new IllegalStateException( 491 "network name and passphrase must be non-empty or empty both."); 492 } 493 if (TextUtils.isEmpty(mNetworkName) 494 && mDeviceAddress.equals(MAC_ANY_ADDRESS)) { 495 throw new IllegalStateException( 496 "peer address must be set if network name and pasphrase are not set."); 497 } 498 499 if (mGroupOperatingFrequency > 0 && mGroupOperatingBand > 0) { 500 throw new IllegalStateException( 501 "Preferred frequency and band are mutually exclusive."); 502 } 503 504 WifiP2pConfig config = new WifiP2pConfig(); 505 config.deviceAddress = mDeviceAddress.toString(); 506 config.networkName = mNetworkName; 507 config.passphrase = mPassphrase; 508 config.groupOwnerBand = GROUP_OWNER_BAND_AUTO; 509 if (mGroupOperatingFrequency > 0) { 510 config.groupOwnerBand = mGroupOperatingFrequency; 511 } else if (mGroupOperatingBand > 0) { 512 config.groupOwnerBand = mGroupOperatingBand; 513 } 514 config.netId = mNetId; 515 return config; 516 } 517 } 518 } 519