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