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