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