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