1 package com.android.server.wifi.hotspot2; 2 3 import static com.android.server.wifi.hotspot2.anqp.Constants.BYTES_IN_EUI48; 4 import static com.android.server.wifi.hotspot2.anqp.Constants.BYTE_MASK; 5 6 import android.net.wifi.ScanResult; 7 import android.util.Log; 8 9 import com.android.server.wifi.hotspot2.anqp.ANQPElement; 10 import com.android.server.wifi.hotspot2.anqp.Constants; 11 import com.android.server.wifi.hotspot2.anqp.RawByteElement; 12 import com.android.server.wifi.util.InformationElementUtil; 13 14 import java.nio.BufferUnderflowException; 15 import java.nio.ByteBuffer; 16 import java.nio.CharBuffer; 17 import java.nio.charset.CharacterCodingException; 18 import java.nio.charset.CharsetDecoder; 19 import java.nio.charset.StandardCharsets; 20 import java.util.ArrayList; 21 import java.util.List; 22 import java.util.Map; 23 24 public class NetworkDetail { 25 26 private static final boolean DBG = false; 27 28 private static final String TAG = "NetworkDetail"; 29 30 public enum Ant { 31 Private, 32 PrivateWithGuest, 33 ChargeablePublic, 34 FreePublic, 35 Personal, 36 EmergencyOnly, 37 Resvd6, 38 Resvd7, 39 Resvd8, 40 Resvd9, 41 Resvd10, 42 Resvd11, 43 Resvd12, 44 Resvd13, 45 TestOrExperimental, 46 Wildcard 47 } 48 49 public enum HSRelease { 50 R1, 51 R2, 52 R3, 53 Unknown 54 } 55 56 // General identifiers: 57 private final String mSSID; 58 private final long mHESSID; 59 private final long mBSSID; 60 // True if the SSID is potentially from a hidden network 61 private final boolean mIsHiddenSsid; 62 63 // BSS Load element: 64 private final int mStationCount; 65 private final int mChannelUtilization; 66 private final int mCapacity; 67 68 //channel detailed information 69 /* 70 * 0 -- 20 MHz 71 * 1 -- 40 MHz 72 * 2 -- 80 MHz 73 * 3 -- 160 MHz 74 * 4 -- 80 + 80 MHz 75 */ 76 private final int mChannelWidth; 77 private final int mPrimaryFreq; 78 private final int mCenterfreq0; 79 private final int mCenterfreq1; 80 81 /* 82 * 802.11 Standard (calculated from Capabilities and Supported Rates) 83 * 0 -- Unknown 84 * 1 -- 802.11a 85 * 2 -- 802.11b 86 * 3 -- 802.11g 87 * 4 -- 802.11n 88 * 7 -- 802.11ac 89 */ 90 private final int mWifiMode; 91 private final int mMaxRate; 92 private final int mMaxNumberSpatialStreams; 93 94 /* 95 * From Interworking element: 96 * mAnt non null indicates the presence of Interworking, i.e. 802.11u 97 */ 98 private final Ant mAnt; 99 private final boolean mInternet; 100 101 /* 102 * From HS20 Indication element: 103 * mHSRelease is null only if the HS20 Indication element was not present. 104 * mAnqpDomainID is set to -1 if not present in the element. 105 */ 106 private final HSRelease mHSRelease; 107 private final int mAnqpDomainID; 108 109 /* 110 * From beacon: 111 * mAnqpOICount is how many additional OIs are available through ANQP. 112 * mRoamingConsortiums is either null, if the element was not present, or is an array of 113 * 1, 2 or 3 longs in which the roaming consortium values occupy the LSBs. 114 */ 115 private final int mAnqpOICount; 116 private final long[] mRoamingConsortiums; 117 private int mDtimInterval = -1; 118 119 private final InformationElementUtil.ExtendedCapabilities mExtendedCapabilities; 120 121 private final Map<Constants.ANQPElementType, ANQPElement> mANQPElements; 122 123 /* 124 * From Wi-Fi Alliance MBO-OCE Information element. 125 * mMboAssociationDisallowedReasonCode is the reason code for AP not accepting new connections 126 * and is set to -1 if association disallowed attribute is not present in the element. 127 */ 128 private final int mMboAssociationDisallowedReasonCode; 129 private final boolean mMboSupported; 130 private final boolean mMboCellularDataAware; 131 private final boolean mOceSupported; 132 NetworkDetail(String bssid, ScanResult.InformationElement[] infoElements, List<String> anqpLines, int freq)133 public NetworkDetail(String bssid, ScanResult.InformationElement[] infoElements, 134 List<String> anqpLines, int freq) { 135 if (infoElements == null) { 136 throw new IllegalArgumentException("Null information elements"); 137 } 138 139 mBSSID = Utils.parseMac(bssid); 140 141 String ssid = null; 142 boolean isHiddenSsid = false; 143 byte[] ssidOctets = null; 144 145 InformationElementUtil.BssLoad bssLoad = new InformationElementUtil.BssLoad(); 146 147 InformationElementUtil.Interworking interworking = 148 new InformationElementUtil.Interworking(); 149 150 InformationElementUtil.RoamingConsortium roamingConsortium = 151 new InformationElementUtil.RoamingConsortium(); 152 153 InformationElementUtil.Vsa vsa = new InformationElementUtil.Vsa(); 154 155 InformationElementUtil.HtOperation htOperation = new InformationElementUtil.HtOperation(); 156 InformationElementUtil.VhtOperation vhtOperation = 157 new InformationElementUtil.VhtOperation(); 158 InformationElementUtil.HeOperation heOperation = new InformationElementUtil.HeOperation(); 159 160 InformationElementUtil.HtCapabilities htCapabilities = 161 new InformationElementUtil.HtCapabilities(); 162 InformationElementUtil.VhtCapabilities vhtCapabilities = 163 new InformationElementUtil.VhtCapabilities(); 164 InformationElementUtil.HeCapabilities heCapabilities = 165 new InformationElementUtil.HeCapabilities(); 166 167 InformationElementUtil.ExtendedCapabilities extendedCapabilities = 168 new InformationElementUtil.ExtendedCapabilities(); 169 170 InformationElementUtil.TrafficIndicationMap trafficIndicationMap = 171 new InformationElementUtil.TrafficIndicationMap(); 172 173 InformationElementUtil.SupportedRates supportedRates = 174 new InformationElementUtil.SupportedRates(); 175 InformationElementUtil.SupportedRates extendedSupportedRates = 176 new InformationElementUtil.SupportedRates(); 177 178 RuntimeException exception = null; 179 180 ArrayList<Integer> iesFound = new ArrayList<Integer>(); 181 try { 182 for (ScanResult.InformationElement ie : infoElements) { 183 iesFound.add(ie.id); 184 switch (ie.id) { 185 case ScanResult.InformationElement.EID_SSID: 186 ssidOctets = ie.bytes; 187 break; 188 case ScanResult.InformationElement.EID_BSS_LOAD: 189 bssLoad.from(ie); 190 break; 191 case ScanResult.InformationElement.EID_HT_OPERATION: 192 htOperation.from(ie); 193 break; 194 case ScanResult.InformationElement.EID_VHT_OPERATION: 195 vhtOperation.from(ie); 196 break; 197 case ScanResult.InformationElement.EID_HT_CAPABILITIES: 198 htCapabilities.from(ie); 199 break; 200 case ScanResult.InformationElement.EID_VHT_CAPABILITIES: 201 vhtCapabilities.from(ie); 202 break; 203 case ScanResult.InformationElement.EID_INTERWORKING: 204 interworking.from(ie); 205 break; 206 case ScanResult.InformationElement.EID_ROAMING_CONSORTIUM: 207 roamingConsortium.from(ie); 208 break; 209 case ScanResult.InformationElement.EID_VSA: 210 vsa.from(ie); 211 break; 212 case ScanResult.InformationElement.EID_EXTENDED_CAPS: 213 extendedCapabilities.from(ie); 214 break; 215 case ScanResult.InformationElement.EID_TIM: 216 trafficIndicationMap.from(ie); 217 break; 218 case ScanResult.InformationElement.EID_SUPPORTED_RATES: 219 supportedRates.from(ie); 220 break; 221 case ScanResult.InformationElement.EID_EXTENDED_SUPPORTED_RATES: 222 extendedSupportedRates.from(ie); 223 break; 224 case ScanResult.InformationElement.EID_EXTENSION_PRESENT: 225 switch(ie.idExt) { 226 case ScanResult.InformationElement.EID_EXT_HE_OPERATION: 227 heOperation.from(ie); 228 break; 229 case ScanResult.InformationElement.EID_EXT_HE_CAPABILITIES: 230 heCapabilities.from(ie); 231 break; 232 default: 233 break; 234 } 235 break; 236 default: 237 break; 238 } 239 } 240 } 241 catch (IllegalArgumentException | BufferUnderflowException | ArrayIndexOutOfBoundsException e) { 242 Log.d(Utils.hs2LogTag(getClass()), "Caught " + e); 243 if (ssidOctets == null) { 244 throw new IllegalArgumentException("Malformed IE string (no SSID)", e); 245 } 246 exception = e; 247 } 248 if (ssidOctets != null) { 249 /* 250 * Strict use of the "UTF-8 SSID" bit by APs appears to be spotty at best even if the 251 * encoding truly is in UTF-8. An unconditional attempt to decode the SSID as UTF-8 is 252 * therefore always made with a fall back to 8859-1 under normal circumstances. 253 * If, however, a previous exception was detected and the UTF-8 bit is set, failure to 254 * decode the SSID will be used as an indication that the whole frame is malformed and 255 * an exception will be triggered. 256 */ 257 CharsetDecoder decoder = StandardCharsets.UTF_8.newDecoder(); 258 try { 259 CharBuffer decoded = decoder.decode(ByteBuffer.wrap(ssidOctets)); 260 ssid = decoded.toString(); 261 } 262 catch (CharacterCodingException cce) { 263 ssid = null; 264 } 265 266 if (ssid == null) { 267 if (extendedCapabilities.isStrictUtf8() && exception != null) { 268 throw new IllegalArgumentException("Failed to decode SSID in dubious IE string"); 269 } 270 else { 271 ssid = new String(ssidOctets, StandardCharsets.ISO_8859_1); 272 } 273 } 274 isHiddenSsid = true; 275 for (byte byteVal : ssidOctets) { 276 if (byteVal != 0) { 277 isHiddenSsid = false; 278 break; 279 } 280 } 281 } 282 283 mSSID = ssid; 284 mHESSID = interworking.hessid; 285 mIsHiddenSsid = isHiddenSsid; 286 mStationCount = bssLoad.stationCount; 287 mChannelUtilization = bssLoad.channelUtilization; 288 mCapacity = bssLoad.capacity; 289 mAnt = interworking.ant; 290 mInternet = interworking.internet; 291 mHSRelease = vsa.hsRelease; 292 mAnqpDomainID = vsa.anqpDomainID; 293 mMboSupported = vsa.IsMboCapable; 294 mMboCellularDataAware = vsa.IsMboApCellularDataAware; 295 mOceSupported = vsa.IsOceCapable; 296 mMboAssociationDisallowedReasonCode = vsa.mboAssociationDisallowedReasonCode; 297 mAnqpOICount = roamingConsortium.anqpOICount; 298 mRoamingConsortiums = roamingConsortium.getRoamingConsortiums(); 299 mExtendedCapabilities = extendedCapabilities; 300 mANQPElements = null; 301 //set up channel info 302 mPrimaryFreq = freq; 303 int channelWidth = ScanResult.UNSPECIFIED; 304 int centerFreq0 = mPrimaryFreq; 305 int centerFreq1 = 0; 306 307 // First check if HE Operation IE is present 308 if (heOperation.isPresent()) { 309 // If 6GHz info is present, then parameters should be acquired from HE Operation IE 310 if (heOperation.is6GhzInfoPresent()) { 311 channelWidth = heOperation.getChannelWidth(); 312 centerFreq0 = heOperation.getCenterFreq0(); 313 centerFreq1 = heOperation.getCenterFreq1(); 314 } else if (heOperation.isVhtInfoPresent()) { 315 // VHT Operation Info could be included inside the HE Operation IE 316 vhtOperation.from(heOperation.getVhtInfoElement()); 317 } 318 } 319 320 // Proceed to VHT Operation IE if parameters were not obtained from HE Operation IE 321 // Not operating in 6GHz 322 if (channelWidth == ScanResult.UNSPECIFIED) { 323 if (vhtOperation.isPresent()) { 324 channelWidth = vhtOperation.getChannelWidth(); 325 if (channelWidth != ScanResult.UNSPECIFIED) { 326 centerFreq0 = vhtOperation.getCenterFreq0(); 327 centerFreq1 = vhtOperation.getCenterFreq1(); 328 } 329 } 330 } 331 332 // Proceed to HT Operation IE if parameters were not obtained from VHT/HE Operation IEs 333 // Apply to operating in 2.4/5GHz with 20/40MHz channels 334 if (channelWidth == ScanResult.UNSPECIFIED) { 335 //Either no vht, or vht shows BW is 40/20 MHz 336 if (htOperation.isPresent()) { 337 channelWidth = htOperation.getChannelWidth(); 338 centerFreq0 = htOperation.getCenterFreq0(mPrimaryFreq); 339 } 340 } 341 342 if (channelWidth == ScanResult.UNSPECIFIED) { 343 // Failed to obtain channel info from HE, VHT, HT IEs (possibly a 802.11a/b/g legacy AP) 344 channelWidth = ScanResult.CHANNEL_WIDTH_20MHZ; 345 } 346 347 mChannelWidth = channelWidth; 348 mCenterfreq0 = centerFreq0; 349 mCenterfreq1 = centerFreq1; 350 351 // If trafficIndicationMap is not valid, mDtimPeriod will be negative 352 if (trafficIndicationMap.isValid()) { 353 mDtimInterval = trafficIndicationMap.mDtimPeriod; 354 } 355 356 mMaxNumberSpatialStreams = Math.max(heCapabilities.getMaxNumberSpatialStreams(), 357 Math.max(vhtCapabilities.getMaxNumberSpatialStreams(), 358 htCapabilities.getMaxNumberSpatialStreams())); 359 360 int maxRateA = 0; 361 int maxRateB = 0; 362 // If we got some Extended supported rates, consider them, if not default to 0 363 if (extendedSupportedRates.isValid()) { 364 // rates are sorted from smallest to largest in InformationElement 365 maxRateB = extendedSupportedRates.mRates.get(extendedSupportedRates.mRates.size() - 1); 366 } 367 // Only process the determination logic if we got a 'SupportedRates' 368 if (supportedRates.isValid()) { 369 maxRateA = supportedRates.mRates.get(supportedRates.mRates.size() - 1); 370 mMaxRate = maxRateA > maxRateB ? maxRateA : maxRateB; 371 mWifiMode = InformationElementUtil.WifiMode.determineMode(mPrimaryFreq, mMaxRate, 372 heOperation.isPresent(), vhtOperation.isPresent(), htOperation.isPresent(), 373 iesFound.contains(ScanResult.InformationElement.EID_ERP)); 374 } else { 375 mWifiMode = 0; 376 mMaxRate = 0; 377 } 378 if (DBG) { 379 Log.d(TAG, mSSID + "ChannelWidth is: " + mChannelWidth + " PrimaryFreq: " 380 + mPrimaryFreq + " Centerfreq0: " + mCenterfreq0 + " Centerfreq1: " 381 + mCenterfreq1 + (extendedCapabilities.is80211McRTTResponder() 382 ? " Support RTT responder" : " Do not support RTT responder") 383 + " MaxNumberSpatialStreams: " + mMaxNumberSpatialStreams 384 + " MboAssociationDisallowedReasonCode: " 385 + mMboAssociationDisallowedReasonCode); 386 Log.v("WifiMode", mSSID 387 + ", WifiMode: " + InformationElementUtil.WifiMode.toString(mWifiMode) 388 + ", Freq: " + mPrimaryFreq 389 + ", MaxRate: " + mMaxRate 390 + ", HE: " + String.valueOf(heOperation.isPresent()) 391 + ", VHT: " + String.valueOf(vhtOperation.isPresent()) 392 + ", HT: " + String.valueOf(htOperation.isPresent()) 393 + ", ERP: " + String.valueOf( 394 iesFound.contains(ScanResult.InformationElement.EID_ERP)) 395 + ", SupportedRates: " + supportedRates.toString() 396 + " ExtendedSupportedRates: " + extendedSupportedRates.toString()); 397 } 398 } 399 getAndAdvancePayload(ByteBuffer data, int plLength)400 private static ByteBuffer getAndAdvancePayload(ByteBuffer data, int plLength) { 401 ByteBuffer payload = data.duplicate().order(data.order()); 402 payload.limit(payload.position() + plLength); 403 data.position(data.position() + plLength); 404 return payload; 405 } 406 NetworkDetail(NetworkDetail base, Map<Constants.ANQPElementType, ANQPElement> anqpElements)407 private NetworkDetail(NetworkDetail base, Map<Constants.ANQPElementType, ANQPElement> anqpElements) { 408 mSSID = base.mSSID; 409 mIsHiddenSsid = base.mIsHiddenSsid; 410 mBSSID = base.mBSSID; 411 mHESSID = base.mHESSID; 412 mStationCount = base.mStationCount; 413 mChannelUtilization = base.mChannelUtilization; 414 mCapacity = base.mCapacity; 415 mAnt = base.mAnt; 416 mInternet = base.mInternet; 417 mHSRelease = base.mHSRelease; 418 mAnqpDomainID = base.mAnqpDomainID; 419 mAnqpOICount = base.mAnqpOICount; 420 mRoamingConsortiums = base.mRoamingConsortiums; 421 mExtendedCapabilities = 422 new InformationElementUtil.ExtendedCapabilities(base.mExtendedCapabilities); 423 mANQPElements = anqpElements; 424 mChannelWidth = base.mChannelWidth; 425 mPrimaryFreq = base.mPrimaryFreq; 426 mCenterfreq0 = base.mCenterfreq0; 427 mCenterfreq1 = base.mCenterfreq1; 428 mDtimInterval = base.mDtimInterval; 429 mWifiMode = base.mWifiMode; 430 mMaxRate = base.mMaxRate; 431 mMaxNumberSpatialStreams = base.mMaxNumberSpatialStreams; 432 mMboSupported = base.mMboSupported; 433 mMboCellularDataAware = base.mMboCellularDataAware; 434 mOceSupported = base.mOceSupported; 435 mMboAssociationDisallowedReasonCode = base.mMboAssociationDisallowedReasonCode; 436 } 437 complete(Map<Constants.ANQPElementType, ANQPElement> anqpElements)438 public NetworkDetail complete(Map<Constants.ANQPElementType, ANQPElement> anqpElements) { 439 return new NetworkDetail(this, anqpElements); 440 } 441 queriable(List<Constants.ANQPElementType> queryElements)442 public boolean queriable(List<Constants.ANQPElementType> queryElements) { 443 return mAnt != null && 444 (Constants.hasBaseANQPElements(queryElements) || 445 Constants.hasR2Elements(queryElements) && mHSRelease == HSRelease.R2); 446 } 447 has80211uInfo()448 public boolean has80211uInfo() { 449 return mAnt != null || mRoamingConsortiums != null || mHSRelease != null; 450 } 451 hasInterworking()452 public boolean hasInterworking() { 453 return mAnt != null; 454 } 455 getSSID()456 public String getSSID() { 457 return mSSID; 458 } 459 getTrimmedSSID()460 public String getTrimmedSSID() { 461 if (mSSID != null) { 462 for (int n = 0; n < mSSID.length(); n++) { 463 if (mSSID.charAt(n) != 0) { 464 return mSSID; 465 } 466 } 467 } 468 return ""; 469 } 470 getHESSID()471 public long getHESSID() { 472 return mHESSID; 473 } 474 getBSSID()475 public long getBSSID() { 476 return mBSSID; 477 } 478 getStationCount()479 public int getStationCount() { 480 return mStationCount; 481 } 482 getChannelUtilization()483 public int getChannelUtilization() { 484 return mChannelUtilization; 485 } 486 getCapacity()487 public int getCapacity() { 488 return mCapacity; 489 } 490 isInterworking()491 public boolean isInterworking() { 492 return mAnt != null; 493 } 494 getAnt()495 public Ant getAnt() { 496 return mAnt; 497 } 498 isInternet()499 public boolean isInternet() { 500 return mInternet; 501 } 502 getHSRelease()503 public HSRelease getHSRelease() { 504 return mHSRelease; 505 } 506 getAnqpDomainID()507 public int getAnqpDomainID() { 508 return mAnqpDomainID; 509 } 510 getOsuProviders()511 public byte[] getOsuProviders() { 512 if (mANQPElements == null) { 513 return null; 514 } 515 ANQPElement osuProviders = mANQPElements.get(Constants.ANQPElementType.HSOSUProviders); 516 return osuProviders != null ? ((RawByteElement) osuProviders).getPayload() : null; 517 } 518 getAnqpOICount()519 public int getAnqpOICount() { 520 return mAnqpOICount; 521 } 522 getRoamingConsortiums()523 public long[] getRoamingConsortiums() { 524 return mRoamingConsortiums; 525 } 526 getANQPElements()527 public Map<Constants.ANQPElementType, ANQPElement> getANQPElements() { 528 return mANQPElements; 529 } 530 getChannelWidth()531 public int getChannelWidth() { 532 return mChannelWidth; 533 } 534 getCenterfreq0()535 public int getCenterfreq0() { 536 return mCenterfreq0; 537 } 538 getCenterfreq1()539 public int getCenterfreq1() { 540 return mCenterfreq1; 541 } 542 getWifiMode()543 public int getWifiMode() { 544 return mWifiMode; 545 } 546 getMaxNumberSpatialStreams()547 public int getMaxNumberSpatialStreams() { 548 return mMaxNumberSpatialStreams; 549 } 550 getDtimInterval()551 public int getDtimInterval() { 552 return mDtimInterval; 553 } 554 is80211McResponderSupport()555 public boolean is80211McResponderSupport() { 556 return mExtendedCapabilities.is80211McRTTResponder(); 557 } 558 isSSID_UTF8()559 public boolean isSSID_UTF8() { 560 return mExtendedCapabilities.isStrictUtf8(); 561 } 562 563 @Override equals(Object thatObject)564 public boolean equals(Object thatObject) { 565 if (this == thatObject) { 566 return true; 567 } 568 if (thatObject == null || getClass() != thatObject.getClass()) { 569 return false; 570 } 571 572 NetworkDetail that = (NetworkDetail)thatObject; 573 574 return getSSID().equals(that.getSSID()) && getBSSID() == that.getBSSID(); 575 } 576 577 @Override hashCode()578 public int hashCode() { 579 return ((mSSID.hashCode() * 31) + (int)(mBSSID >>> 32)) * 31 + (int)mBSSID; 580 } 581 582 @Override toString()583 public String toString() { 584 return String.format("NetworkInfo{SSID='%s', HESSID=%x, BSSID=%x, StationCount=%d, " + 585 "ChannelUtilization=%d, Capacity=%d, Ant=%s, Internet=%s, " + 586 "HSRelease=%s, AnqpDomainID=%d, " + 587 "AnqpOICount=%d, RoamingConsortiums=%s}", 588 mSSID, mHESSID, mBSSID, mStationCount, 589 mChannelUtilization, mCapacity, mAnt, mInternet, 590 mHSRelease, mAnqpDomainID, 591 mAnqpOICount, Utils.roamingConsortiumsToString(mRoamingConsortiums)); 592 } 593 toKeyString()594 public String toKeyString() { 595 return mHESSID != 0 ? 596 String.format("'%s':%012x (%012x)", mSSID, mBSSID, mHESSID) : 597 String.format("'%s':%012x", mSSID, mBSSID); 598 } 599 getBSSIDString()600 public String getBSSIDString() { 601 return toMACString(mBSSID); 602 } 603 604 /** 605 * Evaluates the ScanResult this NetworkDetail is built from 606 * returns true if built from a Beacon Frame 607 * returns false if built from a Probe Response 608 */ isBeaconFrame()609 public boolean isBeaconFrame() { 610 // Beacon frames have a 'Traffic Indication Map' Information element 611 // Probe Responses do not. This is indicated by a DTIM period > 0 612 return mDtimInterval > 0; 613 } 614 615 /** 616 * Evaluates the ScanResult this NetworkDetail is built from 617 * returns true if built from a hidden Beacon Frame 618 * returns false if not hidden or not a Beacon 619 */ isHiddenBeaconFrame()620 public boolean isHiddenBeaconFrame() { 621 // Hidden networks are not 80211 standard, but it is common for a hidden network beacon 622 // frame to either send zero-value bytes as the SSID, or to send no bytes at all. 623 return isBeaconFrame() && mIsHiddenSsid; 624 } 625 toMACString(long mac)626 public static String toMACString(long mac) { 627 StringBuilder sb = new StringBuilder(); 628 boolean first = true; 629 for (int n = BYTES_IN_EUI48 - 1; n >= 0; n--) { 630 if (first) { 631 first = false; 632 } else { 633 sb.append(':'); 634 } 635 sb.append(String.format("%02x", (mac >>> (n * Byte.SIZE)) & BYTE_MASK)); 636 } 637 return sb.toString(); 638 } 639 getMboAssociationDisallowedReasonCode()640 public int getMboAssociationDisallowedReasonCode() { 641 return mMboAssociationDisallowedReasonCode; 642 } 643 isMboSupported()644 public boolean isMboSupported() { 645 return mMboSupported; 646 } 647 isMboCellularDataAware()648 public boolean isMboCellularDataAware() { 649 return mMboCellularDataAware; 650 } 651 isOceSupported()652 public boolean isOceSupported() { 653 return mOceSupported; 654 } 655 } 656