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 = 0; 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 mChannelWidth = channelWidth; 342 mCenterfreq0 = centerFreq0; 343 mCenterfreq1 = centerFreq1; 344 345 // If trafficIndicationMap is not valid, mDtimPeriod will be negative 346 if (trafficIndicationMap.isValid()) { 347 mDtimInterval = trafficIndicationMap.mDtimPeriod; 348 } 349 350 mMaxNumberSpatialStreams = Math.max(heCapabilities.getMaxNumberSpatialStreams(), 351 Math.max(vhtCapabilities.getMaxNumberSpatialStreams(), 352 htCapabilities.getMaxNumberSpatialStreams())); 353 354 int maxRateA = 0; 355 int maxRateB = 0; 356 // If we got some Extended supported rates, consider them, if not default to 0 357 if (extendedSupportedRates.isValid()) { 358 // rates are sorted from smallest to largest in InformationElement 359 maxRateB = extendedSupportedRates.mRates.get(extendedSupportedRates.mRates.size() - 1); 360 } 361 // Only process the determination logic if we got a 'SupportedRates' 362 if (supportedRates.isValid()) { 363 maxRateA = supportedRates.mRates.get(supportedRates.mRates.size() - 1); 364 mMaxRate = maxRateA > maxRateB ? maxRateA : maxRateB; 365 mWifiMode = InformationElementUtil.WifiMode.determineMode(mPrimaryFreq, mMaxRate, 366 heOperation.isPresent(), vhtOperation.isPresent(), htOperation.isPresent(), 367 iesFound.contains(ScanResult.InformationElement.EID_ERP)); 368 } else { 369 mWifiMode = 0; 370 mMaxRate = 0; 371 } 372 if (DBG) { 373 Log.d(TAG, mSSID + "ChannelWidth is: " + mChannelWidth + " PrimaryFreq: " 374 + mPrimaryFreq + " Centerfreq0: " + mCenterfreq0 + " Centerfreq1: " 375 + mCenterfreq1 + (extendedCapabilities.is80211McRTTResponder() 376 ? " Support RTT responder" : " Do not support RTT responder") 377 + " MaxNumberSpatialStreams: " + mMaxNumberSpatialStreams 378 + " MboAssociationDisallowedReasonCode: " 379 + mMboAssociationDisallowedReasonCode); 380 Log.v("WifiMode", mSSID 381 + ", WifiMode: " + InformationElementUtil.WifiMode.toString(mWifiMode) 382 + ", Freq: " + mPrimaryFreq 383 + ", MaxRate: " + mMaxRate 384 + ", HE: " + String.valueOf(heOperation.isPresent()) 385 + ", VHT: " + String.valueOf(vhtOperation.isPresent()) 386 + ", HT: " + String.valueOf(htOperation.isPresent()) 387 + ", ERP: " + String.valueOf( 388 iesFound.contains(ScanResult.InformationElement.EID_ERP)) 389 + ", SupportedRates: " + supportedRates.toString() 390 + " ExtendedSupportedRates: " + extendedSupportedRates.toString()); 391 } 392 } 393 getAndAdvancePayload(ByteBuffer data, int plLength)394 private static ByteBuffer getAndAdvancePayload(ByteBuffer data, int plLength) { 395 ByteBuffer payload = data.duplicate().order(data.order()); 396 payload.limit(payload.position() + plLength); 397 data.position(data.position() + plLength); 398 return payload; 399 } 400 NetworkDetail(NetworkDetail base, Map<Constants.ANQPElementType, ANQPElement> anqpElements)401 private NetworkDetail(NetworkDetail base, Map<Constants.ANQPElementType, ANQPElement> anqpElements) { 402 mSSID = base.mSSID; 403 mIsHiddenSsid = base.mIsHiddenSsid; 404 mBSSID = base.mBSSID; 405 mHESSID = base.mHESSID; 406 mStationCount = base.mStationCount; 407 mChannelUtilization = base.mChannelUtilization; 408 mCapacity = base.mCapacity; 409 mAnt = base.mAnt; 410 mInternet = base.mInternet; 411 mHSRelease = base.mHSRelease; 412 mAnqpDomainID = base.mAnqpDomainID; 413 mAnqpOICount = base.mAnqpOICount; 414 mRoamingConsortiums = base.mRoamingConsortiums; 415 mExtendedCapabilities = 416 new InformationElementUtil.ExtendedCapabilities(base.mExtendedCapabilities); 417 mANQPElements = anqpElements; 418 mChannelWidth = base.mChannelWidth; 419 mPrimaryFreq = base.mPrimaryFreq; 420 mCenterfreq0 = base.mCenterfreq0; 421 mCenterfreq1 = base.mCenterfreq1; 422 mDtimInterval = base.mDtimInterval; 423 mWifiMode = base.mWifiMode; 424 mMaxRate = base.mMaxRate; 425 mMaxNumberSpatialStreams = base.mMaxNumberSpatialStreams; 426 mMboSupported = base.mMboSupported; 427 mMboCellularDataAware = base.mMboCellularDataAware; 428 mOceSupported = base.mOceSupported; 429 mMboAssociationDisallowedReasonCode = base.mMboAssociationDisallowedReasonCode; 430 } 431 complete(Map<Constants.ANQPElementType, ANQPElement> anqpElements)432 public NetworkDetail complete(Map<Constants.ANQPElementType, ANQPElement> anqpElements) { 433 return new NetworkDetail(this, anqpElements); 434 } 435 queriable(List<Constants.ANQPElementType> queryElements)436 public boolean queriable(List<Constants.ANQPElementType> queryElements) { 437 return mAnt != null && 438 (Constants.hasBaseANQPElements(queryElements) || 439 Constants.hasR2Elements(queryElements) && mHSRelease == HSRelease.R2); 440 } 441 has80211uInfo()442 public boolean has80211uInfo() { 443 return mAnt != null || mRoamingConsortiums != null || mHSRelease != null; 444 } 445 hasInterworking()446 public boolean hasInterworking() { 447 return mAnt != null; 448 } 449 getSSID()450 public String getSSID() { 451 return mSSID; 452 } 453 getTrimmedSSID()454 public String getTrimmedSSID() { 455 if (mSSID != null) { 456 for (int n = 0; n < mSSID.length(); n++) { 457 if (mSSID.charAt(n) != 0) { 458 return mSSID; 459 } 460 } 461 } 462 return ""; 463 } 464 getHESSID()465 public long getHESSID() { 466 return mHESSID; 467 } 468 getBSSID()469 public long getBSSID() { 470 return mBSSID; 471 } 472 getStationCount()473 public int getStationCount() { 474 return mStationCount; 475 } 476 getChannelUtilization()477 public int getChannelUtilization() { 478 return mChannelUtilization; 479 } 480 getCapacity()481 public int getCapacity() { 482 return mCapacity; 483 } 484 isInterworking()485 public boolean isInterworking() { 486 return mAnt != null; 487 } 488 getAnt()489 public Ant getAnt() { 490 return mAnt; 491 } 492 isInternet()493 public boolean isInternet() { 494 return mInternet; 495 } 496 getHSRelease()497 public HSRelease getHSRelease() { 498 return mHSRelease; 499 } 500 getAnqpDomainID()501 public int getAnqpDomainID() { 502 return mAnqpDomainID; 503 } 504 getOsuProviders()505 public byte[] getOsuProviders() { 506 if (mANQPElements == null) { 507 return null; 508 } 509 ANQPElement osuProviders = mANQPElements.get(Constants.ANQPElementType.HSOSUProviders); 510 return osuProviders != null ? ((RawByteElement) osuProviders).getPayload() : null; 511 } 512 getAnqpOICount()513 public int getAnqpOICount() { 514 return mAnqpOICount; 515 } 516 getRoamingConsortiums()517 public long[] getRoamingConsortiums() { 518 return mRoamingConsortiums; 519 } 520 getANQPElements()521 public Map<Constants.ANQPElementType, ANQPElement> getANQPElements() { 522 return mANQPElements; 523 } 524 getChannelWidth()525 public int getChannelWidth() { 526 return mChannelWidth; 527 } 528 getCenterfreq0()529 public int getCenterfreq0() { 530 return mCenterfreq0; 531 } 532 getCenterfreq1()533 public int getCenterfreq1() { 534 return mCenterfreq1; 535 } 536 getWifiMode()537 public int getWifiMode() { 538 return mWifiMode; 539 } 540 getMaxNumberSpatialStreams()541 public int getMaxNumberSpatialStreams() { 542 return mMaxNumberSpatialStreams; 543 } 544 getDtimInterval()545 public int getDtimInterval() { 546 return mDtimInterval; 547 } 548 is80211McResponderSupport()549 public boolean is80211McResponderSupport() { 550 return mExtendedCapabilities.is80211McRTTResponder(); 551 } 552 isSSID_UTF8()553 public boolean isSSID_UTF8() { 554 return mExtendedCapabilities.isStrictUtf8(); 555 } 556 557 @Override equals(Object thatObject)558 public boolean equals(Object thatObject) { 559 if (this == thatObject) { 560 return true; 561 } 562 if (thatObject == null || getClass() != thatObject.getClass()) { 563 return false; 564 } 565 566 NetworkDetail that = (NetworkDetail)thatObject; 567 568 return getSSID().equals(that.getSSID()) && getBSSID() == that.getBSSID(); 569 } 570 571 @Override hashCode()572 public int hashCode() { 573 return ((mSSID.hashCode() * 31) + (int)(mBSSID >>> 32)) * 31 + (int)mBSSID; 574 } 575 576 @Override toString()577 public String toString() { 578 return String.format("NetworkInfo{SSID='%s', HESSID=%x, BSSID=%x, StationCount=%d, " + 579 "ChannelUtilization=%d, Capacity=%d, Ant=%s, Internet=%s, " + 580 "HSRelease=%s, AnqpDomainID=%d, " + 581 "AnqpOICount=%d, RoamingConsortiums=%s}", 582 mSSID, mHESSID, mBSSID, mStationCount, 583 mChannelUtilization, mCapacity, mAnt, mInternet, 584 mHSRelease, mAnqpDomainID, 585 mAnqpOICount, Utils.roamingConsortiumsToString(mRoamingConsortiums)); 586 } 587 toKeyString()588 public String toKeyString() { 589 return mHESSID != 0 ? 590 String.format("'%s':%012x (%012x)", mSSID, mBSSID, mHESSID) : 591 String.format("'%s':%012x", mSSID, mBSSID); 592 } 593 getBSSIDString()594 public String getBSSIDString() { 595 return toMACString(mBSSID); 596 } 597 598 /** 599 * Evaluates the ScanResult this NetworkDetail is built from 600 * returns true if built from a Beacon Frame 601 * returns false if built from a Probe Response 602 */ isBeaconFrame()603 public boolean isBeaconFrame() { 604 // Beacon frames have a 'Traffic Indication Map' Information element 605 // Probe Responses do not. This is indicated by a DTIM period > 0 606 return mDtimInterval > 0; 607 } 608 609 /** 610 * Evaluates the ScanResult this NetworkDetail is built from 611 * returns true if built from a hidden Beacon Frame 612 * returns false if not hidden or not a Beacon 613 */ isHiddenBeaconFrame()614 public boolean isHiddenBeaconFrame() { 615 // Hidden networks are not 80211 standard, but it is common for a hidden network beacon 616 // frame to either send zero-value bytes as the SSID, or to send no bytes at all. 617 return isBeaconFrame() && mIsHiddenSsid; 618 } 619 toMACString(long mac)620 public static String toMACString(long mac) { 621 StringBuilder sb = new StringBuilder(); 622 boolean first = true; 623 for (int n = BYTES_IN_EUI48 - 1; n >= 0; n--) { 624 if (first) { 625 first = false; 626 } else { 627 sb.append(':'); 628 } 629 sb.append(String.format("%02x", (mac >>> (n * Byte.SIZE)) & BYTE_MASK)); 630 } 631 return sb.toString(); 632 } 633 getMboAssociationDisallowedReasonCode()634 public int getMboAssociationDisallowedReasonCode() { 635 return mMboAssociationDisallowedReasonCode; 636 } 637 isMboSupported()638 public boolean isMboSupported() { 639 return mMboSupported; 640 } 641 isMboCellularDataAware()642 public boolean isMboCellularDataAware() { 643 return mMboCellularDataAware; 644 } 645 isOceSupported()646 public boolean isOceSupported() { 647 return mOceSupported; 648 } 649 } 650