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.NonNull; 20 import android.annotation.Nullable; 21 import android.compat.annotation.UnsupportedAppUsage; 22 import android.net.wifi.ScanResult; 23 import android.os.Build; 24 import android.os.Parcel; 25 import android.os.Parcelable; 26 import android.util.Log; 27 28 import com.android.modules.utils.build.SdkLevel; 29 30 import java.util.ArrayList; 31 import java.util.Collections; 32 import java.util.List; 33 import java.util.Objects; 34 import java.util.regex.Matcher; 35 import java.util.regex.Pattern; 36 37 /** 38 * A class representing a Wi-Fi p2p device 39 * 40 * Note that the operations are not thread safe 41 * {@see WifiP2pManager} 42 */ 43 public class WifiP2pDevice implements Parcelable { 44 45 private static final String TAG = "WifiP2pDevice"; 46 47 /** 48 * The device name is a user friendly string to identify a Wi-Fi p2p device 49 */ 50 public String deviceName = ""; 51 52 /** 53 * The device MAC address uniquely identifies a Wi-Fi p2p device 54 */ 55 public String deviceAddress = ""; 56 57 /** 58 * Primary device type identifies the type of device. For example, an application 59 * could filter the devices discovered to only display printers if the purpose is to 60 * enable a printing action from the user. See the Wi-Fi Direct technical specification 61 * for the full list of standard device types supported. 62 */ 63 public String primaryDeviceType; 64 65 /** 66 * Secondary device type is an optional attribute that can be provided by a device in 67 * addition to the primary device type. 68 */ 69 public String secondaryDeviceType; 70 71 72 // These definitions match the ones in wpa_supplicant 73 /* WPS config methods supported */ 74 private static final int WPS_CONFIG_DISPLAY = 0x0008; 75 private static final int WPS_CONFIG_PUSHBUTTON = 0x0080; 76 private static final int WPS_CONFIG_KEYPAD = 0x0100; 77 78 /* Device Capability bitmap */ 79 private static final int DEVICE_CAPAB_SERVICE_DISCOVERY = 1; 80 @SuppressWarnings("unused") 81 private static final int DEVICE_CAPAB_CLIENT_DISCOVERABILITY = 1<<1; 82 @SuppressWarnings("unused") 83 private static final int DEVICE_CAPAB_CONCURRENT_OPER = 1<<2; 84 @SuppressWarnings("unused") 85 private static final int DEVICE_CAPAB_INFRA_MANAGED = 1<<3; 86 @SuppressWarnings("unused") 87 private static final int DEVICE_CAPAB_DEVICE_LIMIT = 1<<4; 88 private static final int DEVICE_CAPAB_INVITATION_PROCEDURE = 1<<5; 89 90 /* Group Capability bitmap */ 91 private static final int GROUP_CAPAB_GROUP_OWNER = 1; 92 @SuppressWarnings("unused") 93 private static final int GROUP_CAPAB_PERSISTENT_GROUP = 1<<1; 94 private static final int GROUP_CAPAB_GROUP_LIMIT = 1<<2; 95 @SuppressWarnings("unused") 96 private static final int GROUP_CAPAB_INTRA_BSS_DIST = 1<<3; 97 @SuppressWarnings("unused") 98 private static final int GROUP_CAPAB_CROSS_CONN = 1<<4; 99 @SuppressWarnings("unused") 100 private static final int GROUP_CAPAB_PERSISTENT_RECONN = 1<<5; 101 @SuppressWarnings("unused") 102 private static final int GROUP_CAPAB_GROUP_FORMATION = 1<<6; 103 104 /** 105 * WPS config methods supported 106 * @hide 107 */ 108 @UnsupportedAppUsage 109 public int wpsConfigMethodsSupported; 110 111 /** 112 * Device capability 113 * @hide 114 */ 115 @UnsupportedAppUsage 116 public int deviceCapability; 117 118 /** 119 * Group capability 120 * @hide 121 */ 122 @UnsupportedAppUsage 123 public int groupCapability; 124 125 public static final int CONNECTED = 0; 126 public static final int INVITED = 1; 127 public static final int FAILED = 2; 128 public static final int AVAILABLE = 3; 129 public static final int UNAVAILABLE = 4; 130 131 /** Device connection status */ 132 public int status = UNAVAILABLE; 133 134 /** @hide */ 135 @UnsupportedAppUsage 136 public WifiP2pWfdInfo wfdInfo; 137 138 /** This stores vendor-specific information element from the native side. */ 139 private List<ScanResult.InformationElement> mVendorElements; 140 141 /** Detailed device string pattern with WFD info 142 * Example: 143 * P2P-DEVICE-FOUND 00:18:6b:de:a3:6e p2p_dev_addr=00:18:6b:de:a3:6e 144 * pri_dev_type=1-0050F204-1 name='DWD-300-DEA36E' config_methods=0x188 145 * dev_capab=0x21 group_capab=0x9 146 */ 147 private static final Pattern detailedDevicePattern = Pattern.compile( 148 "((?:[0-9a-f]{2}:){5}[0-9a-f]{2}) " 149 + "(\\d+ )?" 150 + "p2p_dev_addr=((?:[0-9a-f]{2}:){5}[0-9a-f]{2}) " 151 + "pri_dev_type=(\\d+-[0-9a-fA-F]+-\\d+) " 152 + "name='(.*)' " 153 + "config_methods=(0x[0-9a-fA-F]+) " 154 + "dev_capab=(0x[0-9a-fA-F]+) " 155 + "group_capab=(0x[0-9a-fA-F]+)" 156 + "( wfd_dev_info=0x([0-9a-fA-F]{12}))?" 157 + "( wfd_r2_dev_info=0x([0-9a-fA-F]{4}))?" 158 ); 159 160 /** 2 token device address pattern 161 * Example: 162 * P2P-DEVICE-LOST p2p_dev_addr=fa:7b:7a:42:02:13 163 * AP-STA-DISCONNECTED 42:fc:89:a8:96:09 164 */ 165 private static final Pattern twoTokenPattern = Pattern.compile( 166 "(p2p_dev_addr=)?((?:[0-9a-f]{2}:){5}[0-9a-f]{2})" 167 ); 168 169 /** 3 token device address pattern 170 * Example: 171 * AP-STA-CONNECTED 42:fc:89:a8:96:09 p2p_dev_addr=fa:7b:7a:42:02:13 172 * AP-STA-DISCONNECTED 42:fc:89:a8:96:09 p2p_dev_addr=fa:7b:7a:42:02:13 173 */ 174 private static final Pattern threeTokenPattern = Pattern.compile( 175 "(?:[0-9a-f]{2}:){5}[0-9a-f]{2} p2p_dev_addr=((?:[0-9a-f]{2}:){5}[0-9a-f]{2})" 176 ); 177 178 WifiP2pDevice()179 public WifiP2pDevice() { 180 } 181 182 /** 183 * @param string formats supported include 184 * P2P-DEVICE-FOUND fa:7b:7a:42:02:13 p2p_dev_addr=fa:7b:7a:42:02:13 185 * pri_dev_type=1-0050F204-1 name='p2p-TEST1' config_methods=0x188 dev_capab=0x27 186 * group_capab=0x0 wfd_dev_info=000006015d022a0032 187 * 188 * P2P-DEVICE-LOST p2p_dev_addr=fa:7b:7a:42:02:13 189 * 190 * AP-STA-CONNECTED 42:fc:89:a8:96:09 [p2p_dev_addr=02:90:4c:a0:92:54] 191 * 192 * AP-STA-DISCONNECTED 42:fc:89:a8:96:09 [p2p_dev_addr=02:90:4c:a0:92:54] 193 * 194 * fa:7b:7a:42:02:13 195 * 196 * Note: The events formats can be looked up in the wpa_supplicant code 197 * @hide 198 */ 199 @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) WifiP2pDevice(String string)200 public WifiP2pDevice(String string) throws IllegalArgumentException { 201 String[] tokens = string.split("[ \n]"); 202 Matcher match; 203 204 if (tokens.length < 1) { 205 throw new IllegalArgumentException("Malformed supplicant event"); 206 } 207 208 switch (tokens.length) { 209 case 1: 210 /* Just a device address */ 211 deviceAddress = string; 212 return; 213 case 2: 214 match = twoTokenPattern.matcher(string); 215 if (!match.find()) { 216 throw new IllegalArgumentException("Malformed supplicant event"); 217 } 218 deviceAddress = match.group(2); 219 return; 220 case 3: 221 match = threeTokenPattern.matcher(string); 222 if (!match.find()) { 223 throw new IllegalArgumentException("Malformed supplicant event"); 224 } 225 deviceAddress = match.group(1); 226 return; 227 default: 228 match = detailedDevicePattern.matcher(string); 229 if (!match.find()) { 230 throw new IllegalArgumentException("Malformed supplicant event"); 231 } 232 233 deviceAddress = match.group(3); 234 primaryDeviceType = match.group(4); 235 deviceName = match.group(5); 236 wpsConfigMethodsSupported = parseHex(match.group(6)); 237 deviceCapability = parseHex(match.group(7)); 238 groupCapability = parseHex(match.group(8)); 239 if (match.group(9) != null) { 240 String str = match.group(10); 241 if (null == str) break; 242 wfdInfo = new WifiP2pWfdInfo(parseHex(str.substring(0,4)), 243 parseHex(str.substring(4,8)), 244 parseHex(str.substring(8,12))); 245 if (match.group(11) != null && SdkLevel.isAtLeastS()) { 246 String r2str = match.group(12); 247 if (null == r2str) break; 248 wfdInfo.setR2DeviceType(parseHex(r2str.substring(0, 4))); 249 } 250 } 251 break; 252 } 253 254 if (tokens[0].startsWith("P2P-DEVICE-FOUND")) { 255 status = AVAILABLE; 256 } 257 } 258 259 /** The Wifi Display information for this device, or null if unavailable. */ 260 @Nullable getWfdInfo()261 public WifiP2pWfdInfo getWfdInfo() { 262 return wfdInfo; 263 } 264 265 /** Returns true if WPS push button configuration is supported */ wpsPbcSupported()266 public boolean wpsPbcSupported() { 267 return (wpsConfigMethodsSupported & WPS_CONFIG_PUSHBUTTON) != 0; 268 } 269 270 /** Returns true if WPS keypad configuration is supported */ wpsKeypadSupported()271 public boolean wpsKeypadSupported() { 272 return (wpsConfigMethodsSupported & WPS_CONFIG_KEYPAD) != 0; 273 } 274 275 /** Returns true if WPS display configuration is supported */ wpsDisplaySupported()276 public boolean wpsDisplaySupported() { 277 return (wpsConfigMethodsSupported & WPS_CONFIG_DISPLAY) != 0; 278 } 279 280 /** Returns true if the device is capable of service discovery */ isServiceDiscoveryCapable()281 public boolean isServiceDiscoveryCapable() { 282 return (deviceCapability & DEVICE_CAPAB_SERVICE_DISCOVERY) != 0; 283 } 284 285 /** Returns true if the device is capable of invitation {@hide}*/ isInvitationCapable()286 public boolean isInvitationCapable() { 287 return (deviceCapability & DEVICE_CAPAB_INVITATION_PROCEDURE) != 0; 288 } 289 290 /** Returns true if the device reaches the limit. {@hide}*/ isDeviceLimit()291 public boolean isDeviceLimit() { 292 return (deviceCapability & DEVICE_CAPAB_DEVICE_LIMIT) != 0; 293 } 294 295 /** Returns true if the device is a group owner */ isGroupOwner()296 public boolean isGroupOwner() { 297 return (groupCapability & GROUP_CAPAB_GROUP_OWNER) != 0; 298 } 299 300 /** Returns true if the group reaches the limit. {@hide}*/ isGroupLimit()301 public boolean isGroupLimit() { 302 return (groupCapability & GROUP_CAPAB_GROUP_LIMIT) != 0; 303 } 304 305 /** 306 * Update this device's details using another {@link WifiP2pDevice} instance. 307 * This will throw an exception if the device address does not match. 308 * 309 * @param device another instance of {@link WifiP2pDevice} used to update this instance. 310 * @throws IllegalArgumentException if the device is null or the device address does not match 311 */ update(@onNull WifiP2pDevice device)312 public void update(@NonNull WifiP2pDevice device) { 313 updateSupplicantDetails(device); 314 status = device.status; 315 } 316 317 /** Updates details obtained from supplicant @hide */ updateSupplicantDetails(WifiP2pDevice device)318 public void updateSupplicantDetails(WifiP2pDevice device) { 319 if (device == null) { 320 throw new IllegalArgumentException("device is null"); 321 } 322 if (device.deviceAddress == null) { 323 throw new IllegalArgumentException("deviceAddress is null"); 324 } 325 if (!deviceAddress.equals(device.deviceAddress)) { 326 throw new IllegalArgumentException("deviceAddress does not match"); 327 } 328 deviceName = device.deviceName; 329 primaryDeviceType = device.primaryDeviceType; 330 secondaryDeviceType = device.secondaryDeviceType; 331 wpsConfigMethodsSupported = device.wpsConfigMethodsSupported; 332 deviceCapability = device.deviceCapability; 333 groupCapability = device.groupCapability; 334 wfdInfo = device.wfdInfo; 335 } 336 337 /** 338 * Set vendor-specific information elements. 339 * @hide 340 */ setVendorElements( List<ScanResult.InformationElement> vendorElements)341 public void setVendorElements( 342 List<ScanResult.InformationElement> vendorElements) { 343 if (vendorElements == null) { 344 mVendorElements = null; 345 return; 346 } 347 mVendorElements = new ArrayList<>(vendorElements); 348 } 349 350 /** 351 * Get the vendor-specific information elements received as part of the discovery 352 * of the peer device. 353 * 354 * @return the list of vendor-specific information elements 355 * The information element format is defined in the IEEE 802.11-2016 spec 356 * Table 9-77. 357 */ getVendorElements()358 @NonNull public List<ScanResult.InformationElement> getVendorElements() { 359 if (mVendorElements == null) return Collections.emptyList(); 360 return new ArrayList<>(mVendorElements); 361 } 362 363 @Override equals(Object obj)364 public boolean equals(Object obj) { 365 if (this == obj) return true; 366 if (!(obj instanceof WifiP2pDevice)) return false; 367 368 WifiP2pDevice other = (WifiP2pDevice) obj; 369 if (other == null || other.deviceAddress == null) { 370 return (deviceAddress == null); 371 } 372 return other.deviceAddress.equals(deviceAddress); 373 } 374 375 @Override hashCode()376 public int hashCode() { 377 return Objects.hashCode(deviceAddress); 378 } 379 380 @Override toString()381 public String toString() { 382 StringBuffer sbuf = new StringBuffer(); 383 sbuf.append("Device: ").append(deviceName); 384 sbuf.append("\n deviceAddress: ").append(deviceAddress); 385 sbuf.append("\n primary type: ").append(primaryDeviceType); 386 sbuf.append("\n secondary type: ").append(secondaryDeviceType); 387 sbuf.append("\n wps: ").append(wpsConfigMethodsSupported); 388 sbuf.append("\n grpcapab: ").append(groupCapability); 389 sbuf.append("\n devcapab: ").append(deviceCapability); 390 sbuf.append("\n status: ").append(status); 391 sbuf.append("\n wfdInfo: ").append(wfdInfo); 392 sbuf.append("\n vendorElements: ").append(mVendorElements); 393 return sbuf.toString(); 394 } 395 396 /** Implement the Parcelable interface */ 397 @Override describeContents()398 public int describeContents() { 399 return 0; 400 } 401 402 /** copy constructor */ WifiP2pDevice(WifiP2pDevice source)403 public WifiP2pDevice(WifiP2pDevice source) { 404 if (source != null) { 405 deviceName = source.deviceName; 406 deviceAddress = source.deviceAddress; 407 primaryDeviceType = source.primaryDeviceType; 408 secondaryDeviceType = source.secondaryDeviceType; 409 wpsConfigMethodsSupported = source.wpsConfigMethodsSupported; 410 deviceCapability = source.deviceCapability; 411 groupCapability = source.groupCapability; 412 status = source.status; 413 if (source.wfdInfo != null) { 414 wfdInfo = new WifiP2pWfdInfo(source.wfdInfo); 415 } 416 if (null != source.mVendorElements) { 417 mVendorElements = new ArrayList<>(source.mVendorElements); 418 } 419 } 420 } 421 422 /** Implement the Parcelable interface */ 423 @Override writeToParcel(Parcel dest, int flags)424 public void writeToParcel(Parcel dest, int flags) { 425 dest.writeString(deviceName); 426 dest.writeString(deviceAddress); 427 dest.writeString(primaryDeviceType); 428 dest.writeString(secondaryDeviceType); 429 dest.writeInt(wpsConfigMethodsSupported); 430 dest.writeInt(deviceCapability); 431 dest.writeInt(groupCapability); 432 dest.writeInt(status); 433 if (wfdInfo != null) { 434 dest.writeInt(1); 435 wfdInfo.writeToParcel(dest, flags); 436 } else { 437 dest.writeInt(0); 438 } 439 dest.writeTypedList(mVendorElements); 440 } 441 442 /** Implement the Parcelable interface */ 443 public static final @android.annotation.NonNull Creator<WifiP2pDevice> CREATOR = 444 new Creator<WifiP2pDevice>() { 445 @Override 446 public WifiP2pDevice createFromParcel(Parcel in) { 447 WifiP2pDevice device = new WifiP2pDevice(); 448 device.deviceName = in.readString(); 449 device.deviceAddress = in.readString(); 450 device.primaryDeviceType = in.readString(); 451 device.secondaryDeviceType = in.readString(); 452 device.wpsConfigMethodsSupported = in.readInt(); 453 device.deviceCapability = in.readInt(); 454 device.groupCapability = in.readInt(); 455 device.status = in.readInt(); 456 if (in.readInt() == 1) { 457 device.wfdInfo = WifiP2pWfdInfo.CREATOR.createFromParcel(in); 458 } 459 device.mVendorElements = in.createTypedArrayList( 460 ScanResult.InformationElement.CREATOR); 461 return device; 462 } 463 464 @Override 465 public WifiP2pDevice[] newArray(int size) { 466 return new WifiP2pDevice[size]; 467 } 468 }; 469 470 //supported formats: 0x1abc, 0X1abc, 1abc parseHex(String hexString)471 private int parseHex(String hexString) { 472 int num = 0; 473 if (hexString.startsWith("0x") || hexString.startsWith("0X")) { 474 hexString = hexString.substring(2); 475 } 476 477 try { 478 num = Integer.parseInt(hexString, 16); 479 } catch(NumberFormatException e) { 480 Log.e(TAG, "Failed to parse hex string " + hexString); 481 } 482 return num; 483 } 484 } 485