1 /** 2 * Copyright (c) 2016, 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.hotspot2; 18 19 import static android.net.wifi.WifiConfiguration.METERED_OVERRIDE_NONE; 20 import static android.net.wifi.WifiConfiguration.MeteredOverride; 21 22 import android.annotation.CurrentTimeMillisLong; 23 import android.annotation.NonNull; 24 import android.annotation.Nullable; 25 import android.annotation.SystemApi; 26 import android.net.wifi.WifiManager; 27 import android.net.wifi.hotspot2.pps.Credential; 28 import android.net.wifi.hotspot2.pps.HomeSp; 29 import android.net.wifi.hotspot2.pps.Policy; 30 import android.net.wifi.hotspot2.pps.UpdateParameter; 31 import android.os.Build; 32 import android.os.Bundle; 33 import android.os.Parcel; 34 import android.os.ParcelUuid; 35 import android.os.Parcelable; 36 import android.telephony.SubscriptionInfo; 37 import android.telephony.SubscriptionManager; 38 import android.telephony.TelephonyManager; 39 import android.text.TextUtils; 40 import android.util.EventLog; 41 import android.util.Log; 42 43 import androidx.annotation.RequiresApi; 44 45 import com.android.modules.utils.build.SdkLevel; 46 47 import java.nio.charset.StandardCharsets; 48 import java.util.Arrays; 49 import java.util.Collections; 50 import java.util.Date; 51 import java.util.HashMap; 52 import java.util.Locale; 53 import java.util.Map; 54 import java.util.Objects; 55 56 /** 57 * Class representing Passpoint configuration. This contains configurations specified in 58 * PerProviderSubscription (PPS) Management Object (MO) tree. 59 * 60 * For more info, refer to Hotspot 2.0 PPS MO defined in section 9.1 of the Hotspot 2.0 61 * Release 2 Technical Specification. 62 */ 63 public final class PasspointConfiguration implements Parcelable { 64 private static final String TAG = "PasspointConfiguration"; 65 66 /** 67 * Number of bytes for certificate SHA-256 fingerprint byte array. 68 */ 69 private static final int CERTIFICATE_SHA256_BYTES = 32; 70 71 /** 72 * Maximum bytes for URL string. 73 * @hide 74 */ 75 public static final int MAX_URL_BYTES = 2048; 76 77 /** 78 * Maximum size for match entry, just to limit the size of the Passpoint config. 79 * @hide 80 */ 81 public static final int MAX_NUMBER_OF_ENTRIES = 16; 82 83 /** 84 * Maximum size for OI entry. 85 * The spec allows a string of up to 255 characters, with comma delimited numbers like 86 * 001122,334455. So with minimum OI size of 7, the maximum amount of OIs is 36. 87 * @hide 88 */ 89 public static final int MAX_NUMBER_OF_OI = 36; 90 91 92 /** 93 * Maximum bytes for a string entry like FQDN and friendly name. 94 * @hide 95 */ 96 public static final int MAX_STRING_LENGTH = 255; 97 98 /** 99 * HESSID is 48 bit. 100 * @hide 101 */ 102 public static final long MAX_HESSID_VALUE = ((long) 1 << 48) - 1; 103 104 /** 105 * Organization Identifiers is 3 or 5 Octets. 24 or 36 bit. 106 * @hide 107 */ 108 public static final long MAX_OI_VALUE = ((long) 1 << 40) - 1; 109 110 /** 111 * Integer value used for indicating null value in the Parcel. 112 */ 113 private static final int NULL_VALUE = -1; 114 115 /** 116 * Configurations under HomeSp subtree. 117 */ 118 private HomeSp mHomeSp = null; 119 120 /** 121 * Set the Home SP (Service Provider) information. 122 * 123 * @param homeSp The Home SP information to set to 124 */ setHomeSp(HomeSp homeSp)125 public void setHomeSp(HomeSp homeSp) { mHomeSp = homeSp; } 126 /** 127 * Get the Home SP (Service Provider) information. 128 * 129 * @return Home SP information 130 */ getHomeSp()131 public HomeSp getHomeSp() { return mHomeSp; } 132 133 /** 134 * Configurations under AAAServerTrustedNames subtree. 135 */ 136 private String[] mAaaServerTrustedNames = null; 137 /** 138 * Set the AAA server trusted names information. 139 * 140 * @param aaaServerTrustedNames The AAA server trusted names information to set to 141 * @hide 142 */ setAaaServerTrustedNames(@ullable String[] aaaServerTrustedNames)143 public void setAaaServerTrustedNames(@Nullable String[] aaaServerTrustedNames) { 144 mAaaServerTrustedNames = aaaServerTrustedNames; 145 } 146 /** 147 * Get the AAA server trusted names information. 148 * 149 * @return AAA server trusted names information 150 * @hide 151 */ getAaaServerTrustedNames()152 public @Nullable String[] getAaaServerTrustedNames() { 153 return mAaaServerTrustedNames; 154 } 155 156 /** 157 * Configurations under Credential subtree. 158 */ 159 private Credential mCredential = null; 160 /** 161 * Set the credential information. 162 * 163 * @param credential The credential information to set to 164 */ setCredential(Credential credential)165 public void setCredential(Credential credential) { 166 mCredential = credential; 167 } 168 /** 169 * Get the credential information. 170 * 171 * @return credential information 172 */ getCredential()173 public Credential getCredential() { 174 return mCredential; 175 } 176 177 /** 178 * Configurations under Policy subtree. 179 */ 180 private Policy mPolicy = null; 181 /** 182 * @hide 183 */ setPolicy(Policy policy)184 public void setPolicy(Policy policy) { 185 mPolicy = policy; 186 } 187 /** 188 * @hide 189 */ getPolicy()190 public Policy getPolicy() { 191 return mPolicy; 192 } 193 194 /** 195 * Meta data for performing subscription update. 196 */ 197 private UpdateParameter mSubscriptionUpdate = null; 198 /** 199 * @hide 200 */ setSubscriptionUpdate(UpdateParameter subscriptionUpdate)201 public void setSubscriptionUpdate(UpdateParameter subscriptionUpdate) { 202 mSubscriptionUpdate = subscriptionUpdate; 203 } 204 /** 205 * @hide 206 */ getSubscriptionUpdate()207 public UpdateParameter getSubscriptionUpdate() { 208 return mSubscriptionUpdate; 209 } 210 211 /** 212 * List of HTTPS URL for retrieving trust root certificate and the corresponding SHA-256 213 * fingerprint of the certificate. The certificates are used for verifying AAA server's 214 * identity during EAP authentication. 215 */ 216 private Map<String, byte[]> mTrustRootCertList = null; 217 /** 218 * @hide 219 */ setTrustRootCertList(Map<String, byte[]> trustRootCertList)220 public void setTrustRootCertList(Map<String, byte[]> trustRootCertList) { 221 mTrustRootCertList = trustRootCertList; 222 } 223 /** 224 * @hide 225 */ getTrustRootCertList()226 public Map<String, byte[]> getTrustRootCertList() { 227 return mTrustRootCertList; 228 } 229 230 /** 231 * Set by the subscription server, updated every time the configuration is updated by 232 * the subscription server. 233 * 234 * Use Integer.MIN_VALUE to indicate unset value. 235 */ 236 private int mUpdateIdentifier = Integer.MIN_VALUE; 237 /** 238 * @hide 239 */ setUpdateIdentifier(int updateIdentifier)240 public void setUpdateIdentifier(int updateIdentifier) { 241 mUpdateIdentifier = updateIdentifier; 242 } 243 /** 244 * @hide 245 */ getUpdateIdentifier()246 public int getUpdateIdentifier() { 247 return mUpdateIdentifier; 248 } 249 250 /** 251 * The priority of the credential. 252 * 253 * Use Integer.MIN_VALUE to indicate unset value. 254 */ 255 private int mCredentialPriority = Integer.MIN_VALUE; 256 /** 257 * @hide 258 */ setCredentialPriority(int credentialPriority)259 public void setCredentialPriority(int credentialPriority) { 260 mCredentialPriority = credentialPriority; 261 } 262 /** 263 * @hide 264 */ getCredentialPriority()265 public int getCredentialPriority() { 266 return mCredentialPriority; 267 } 268 269 /** 270 * The time this subscription is created. It is in the format of number 271 * of milliseconds since January 1, 1970, 00:00:00 GMT. 272 * 273 * Use Long.MIN_VALUE to indicate unset value. 274 */ 275 private long mSubscriptionCreationTimeInMillis = Long.MIN_VALUE; 276 /** 277 * @hide 278 */ setSubscriptionCreationTimeInMillis(long subscriptionCreationTimeInMillis)279 public void setSubscriptionCreationTimeInMillis(long subscriptionCreationTimeInMillis) { 280 mSubscriptionCreationTimeInMillis = subscriptionCreationTimeInMillis; 281 } 282 /** 283 * @hide 284 */ getSubscriptionCreationTimeInMillis()285 public long getSubscriptionCreationTimeInMillis() { 286 return mSubscriptionCreationTimeInMillis; 287 } 288 289 /** 290 * The time this subscription will expire. It is in the format of number 291 * of milliseconds since January 1, 1970, 00:00:00 GMT. 292 * 293 * Use Long.MIN_VALUE to indicate unset value. 294 */ 295 private long mSubscriptionExpirationTimeMillis = Long.MIN_VALUE; 296 297 /** 298 * Utility method to set the time this subscription will expire. The framework will not attempt 299 * to auto-connect to networks using expired subscriptions. 300 * @param subscriptionExpirationTimeInMillis The expiration time in the format of number of 301 * milliseconds since January 1, 1970, 00:00:00 GMT, 302 * or {@link Long#MIN_VALUE} to unset. 303 */ setSubscriptionExpirationTimeInMillis(@urrentTimeMillisLong long subscriptionExpirationTimeInMillis)304 public void setSubscriptionExpirationTimeInMillis(@CurrentTimeMillisLong 305 long subscriptionExpirationTimeInMillis) { 306 mSubscriptionExpirationTimeMillis = subscriptionExpirationTimeInMillis; 307 } 308 309 /** 310 * Utility method to get the time this subscription will expire. It is in the format of number 311 * of milliseconds since January 1, 1970, 00:00:00 GMT. 312 * 313 * @return The time this subscription will expire, or Long.MIN_VALUE to indicate unset value 314 */ 315 @CurrentTimeMillisLong getSubscriptionExpirationTimeMillis()316 public long getSubscriptionExpirationTimeMillis() { 317 return mSubscriptionExpirationTimeMillis; 318 } 319 320 /** 321 * The type of the subscription. This is defined by the provider and the value is provider 322 * specific. 323 */ 324 private String mSubscriptionType = null; 325 /** 326 * @hide 327 */ setSubscriptionType(String subscriptionType)328 public void setSubscriptionType(String subscriptionType) { 329 mSubscriptionType = subscriptionType; 330 } 331 /** 332 * @hide 333 */ getSubscriptionType()334 public String getSubscriptionType() { 335 return mSubscriptionType; 336 } 337 338 /** 339 * The time period for usage statistics accumulation. A value of zero means that usage 340 * statistics are not accumulated on a periodic basis (e.g., a one-time limit for 341 * “pay as you go” - PAYG service). A non-zero value specifies the usage interval in minutes. 342 */ 343 private long mUsageLimitUsageTimePeriodInMinutes = Long.MIN_VALUE; 344 /** 345 * @hide 346 */ setUsageLimitUsageTimePeriodInMinutes(long usageLimitUsageTimePeriodInMinutes)347 public void setUsageLimitUsageTimePeriodInMinutes(long usageLimitUsageTimePeriodInMinutes) { 348 mUsageLimitUsageTimePeriodInMinutes = usageLimitUsageTimePeriodInMinutes; 349 } 350 /** 351 * @hide 352 */ getUsageLimitUsageTimePeriodInMinutes()353 public long getUsageLimitUsageTimePeriodInMinutes() { 354 return mUsageLimitUsageTimePeriodInMinutes; 355 } 356 357 /** 358 * The time at which usage statistic accumulation begins. It is in the format of number 359 * of milliseconds since January 1, 1970, 00:00:00 GMT. 360 * 361 * Use Long.MIN_VALUE to indicate unset value. 362 */ 363 private long mUsageLimitStartTimeInMillis = Long.MIN_VALUE; 364 /** 365 * @hide 366 */ setUsageLimitStartTimeInMillis(long usageLimitStartTimeInMillis)367 public void setUsageLimitStartTimeInMillis(long usageLimitStartTimeInMillis) { 368 mUsageLimitStartTimeInMillis = usageLimitStartTimeInMillis; 369 } 370 /** 371 * @hide 372 */ getUsageLimitStartTimeInMillis()373 public long getUsageLimitStartTimeInMillis() { 374 return mUsageLimitStartTimeInMillis; 375 } 376 377 /** 378 * The cumulative data limit in megabytes for the {@link #usageLimitUsageTimePeriodInMinutes}. 379 * A value of zero indicate unlimited data usage. 380 * 381 * Use Long.MIN_VALUE to indicate unset value. 382 */ 383 private long mUsageLimitDataLimit = Long.MIN_VALUE; 384 /** 385 * @hide 386 */ setUsageLimitDataLimit(long usageLimitDataLimit)387 public void setUsageLimitDataLimit(long usageLimitDataLimit) { 388 mUsageLimitDataLimit = usageLimitDataLimit; 389 } 390 /** 391 * @hide 392 */ getUsageLimitDataLimit()393 public long getUsageLimitDataLimit() { 394 return mUsageLimitDataLimit; 395 } 396 397 /** 398 * The cumulative time limit in minutes for the {@link #usageLimitUsageTimePeriodInMinutes}. 399 * A value of zero indicate unlimited time usage. 400 */ 401 private long mUsageLimitTimeLimitInMinutes = Long.MIN_VALUE; 402 /** 403 * @hide 404 */ setUsageLimitTimeLimitInMinutes(long usageLimitTimeLimitInMinutes)405 public void setUsageLimitTimeLimitInMinutes(long usageLimitTimeLimitInMinutes) { 406 mUsageLimitTimeLimitInMinutes = usageLimitTimeLimitInMinutes; 407 } 408 /** 409 * @hide 410 */ getUsageLimitTimeLimitInMinutes()411 public long getUsageLimitTimeLimitInMinutes() { 412 return mUsageLimitTimeLimitInMinutes; 413 } 414 415 /** 416 * The map of OSU service provider names whose each element is presented in different 417 * languages for the service provider, which is used for finding a matching 418 * PasspointConfiguration with a given service provider name. 419 */ 420 private Map<String, String> mServiceFriendlyNames = null; 421 422 /** 423 * @hide 424 */ setServiceFriendlyNames(Map<String, String> serviceFriendlyNames)425 public void setServiceFriendlyNames(Map<String, String> serviceFriendlyNames) { 426 mServiceFriendlyNames = serviceFriendlyNames; 427 } 428 429 /** 430 * @hide 431 */ getServiceFriendlyNames()432 public Map<String, String> getServiceFriendlyNames() { 433 return mServiceFriendlyNames; 434 } 435 436 /** 437 * Return the friendly Name for current language from the list of friendly names of OSU 438 * provider. 439 * The string matching the default locale will be returned if it is found, otherwise the 440 * first string in the list will be returned. A null will be returned if the list is empty. 441 * 442 * @return String matching the default locale, null otherwise 443 * @hide 444 */ getServiceFriendlyName()445 public String getServiceFriendlyName() { 446 if (mServiceFriendlyNames == null || mServiceFriendlyNames.isEmpty()) return null; 447 String lang = Locale.getDefault().getLanguage(); 448 String friendlyName = mServiceFriendlyNames.get(lang); 449 if (friendlyName != null) { 450 return friendlyName; 451 } 452 friendlyName = mServiceFriendlyNames.get("en"); 453 if (friendlyName != null) { 454 return friendlyName; 455 } 456 return mServiceFriendlyNames.get(mServiceFriendlyNames.keySet().stream().findFirst().get()); 457 } 458 459 /** 460 * The carrier ID identifies the operator who provides this network configuration. 461 * see {@link TelephonyManager#getSimCarrierId()} 462 */ 463 private int mCarrierId = TelephonyManager.UNKNOWN_CARRIER_ID; 464 465 /** 466 * The subscription ID identifies the SIM card who provides this network configuration. 467 * See {@link SubscriptionInfo#getSubscriptionId()} 468 */ 469 private int mSubscriptionId = SubscriptionManager.INVALID_SUBSCRIPTION_ID; 470 471 private ParcelUuid mSubscriptionGroup = null; 472 473 /** 474 * Set the carrier ID associated with current configuration. 475 * @param carrierId {@code mCarrierId} 476 * @hide 477 */ setCarrierId(int carrierId)478 public void setCarrierId(int carrierId) { 479 this.mCarrierId = carrierId; 480 } 481 482 /** 483 * Get the carrier ID associated with current configuration. 484 * @return {@code mCarrierId} 485 * @hide 486 */ getCarrierId()487 public int getCarrierId() { 488 return mCarrierId; 489 } 490 491 /** 492 * Set the subscription ID associated with current configuration. 493 * @param subscriptionId {@code mSubscriptionId} 494 * @hide 495 */ setSubscriptionId(int subscriptionId)496 public void setSubscriptionId(int subscriptionId) { 497 this.mSubscriptionId = subscriptionId; 498 } 499 500 /** 501 * Get the carrier ID associated with current configuration. 502 * @return {@code mSubscriptionId} 503 * @hide 504 */ getSubscriptionId()505 public int getSubscriptionId() { 506 return mSubscriptionId; 507 } 508 509 /** 510 * Set the subscription group uuid associated with current configuration. 511 * @hide 512 */ setSubscriptionGroup(ParcelUuid subscriptionGroup)513 public void setSubscriptionGroup(ParcelUuid subscriptionGroup) { 514 this.mSubscriptionGroup = subscriptionGroup; 515 } 516 517 /** 518 * Get the subscription group uuid associated with current configuration. 519 * @hide 520 */ getSubscriptionGroup()521 public ParcelUuid getSubscriptionGroup() { 522 return this.mSubscriptionGroup; 523 } 524 525 /** 526 * The auto-join configuration specifies whether or not the Passpoint Configuration is 527 * considered for auto-connection. If true then yes, if false then it isn't considered as part 528 * of auto-connection - but can still be manually connected to. 529 */ 530 private boolean mIsAutojoinEnabled = true; 531 532 /** 533 * The mac randomization setting specifies whether a randomized or device MAC address will 534 * be used to connect to the passpoint network. If true, a randomized MAC will be used. 535 * Otherwise, the device MAC address will be used. 536 */ 537 private boolean mIsMacRandomizationEnabled = true; 538 539 /** 540 * Whether this passpoint configuration should use non-persistent MAC randomization. 541 */ 542 private boolean mIsNonPersistentMacRandomizationEnabled = false; 543 544 545 /** 546 * Indicate whether the network is oem paid or not. Networks are considered oem paid 547 * if the corresponding connection is only available to system apps. 548 * @hide 549 */ 550 private boolean mIsOemPaid; 551 552 /** 553 * Indicate whether the network is oem private or not. Networks are considered oem private 554 * if the corresponding connection is only available to system apps. 555 * @hide 556 */ 557 private boolean mIsOemPrivate; 558 559 /** 560 * Indicate whether or not the network is a carrier merged network. 561 * @hide 562 */ 563 private boolean mIsCarrierMerged; 564 565 /** 566 * Indicates if the end user has expressed an explicit opinion about the 567 * meteredness of this network, such as through the Settings app. 568 * This value is one of {@link #METERED_OVERRIDE_NONE}, {@link #METERED_OVERRIDE_METERED}, 569 * or {@link #METERED_OVERRIDE_NOT_METERED}. 570 * <p> 571 * This should always override any values from {@link WifiInfo#getMeteredHint()}. 572 * 573 * By default this field is set to {@link #METERED_OVERRIDE_NONE}. 574 */ 575 private int mMeteredOverride = METERED_OVERRIDE_NONE; 576 577 private String mDecoratedIdentityPrefix; 578 579 /** 580 * Configures the auto-association status of this Passpoint configuration. A value of true 581 * indicates that the configuration will be considered for auto-connection, a value of false 582 * indicates that only manual connection will work - the framework will not auto-associate to 583 * this Passpoint network. 584 * 585 * @param autojoinEnabled true to be considered for framework auto-connection, false otherwise. 586 * @hide 587 */ setAutojoinEnabled(boolean autojoinEnabled)588 public void setAutojoinEnabled(boolean autojoinEnabled) { 589 mIsAutojoinEnabled = autojoinEnabled; 590 } 591 592 /** 593 * Configures the MAC randomization setting for this Passpoint configuration. 594 * If set to true, the framework will use a randomized MAC address to connect to this Passpoint 595 * network. Otherwise, the framework will use the device MAC address. 596 * 597 * @param enabled true to use randomized MAC address, false to use device MAC address. 598 * @hide 599 */ setMacRandomizationEnabled(boolean enabled)600 public void setMacRandomizationEnabled(boolean enabled) { 601 mIsMacRandomizationEnabled = enabled; 602 } 603 604 /** 605 * This setting is only applicable if MAC randomization is enabled. 606 * If set to true, the framework will periodically generate new MAC addresses for new 607 * connections. 608 * If set to false (the default), the framework will use the same locally generated MAC address 609 * for connections to this passpoint configuration. 610 * @param enabled true to use non-persistent MAC randomization, false to use persistent MAC 611 * randomization. 612 * @hide 613 */ setNonPersistentMacRandomizationEnabled(boolean enabled)614 public void setNonPersistentMacRandomizationEnabled(boolean enabled) { 615 mIsNonPersistentMacRandomizationEnabled = enabled; 616 } 617 618 /** 619 * Sets the metered override setting for this Passpoint configuration. 620 * 621 * @param meteredOverride One of the values in {@link MeteredOverride} 622 * @hide 623 */ setMeteredOverride(@eteredOverride int meteredOverride)624 public void setMeteredOverride(@MeteredOverride int meteredOverride) { 625 mMeteredOverride = meteredOverride; 626 } 627 628 /** 629 * Indicates whether the Passpoint configuration may be auto-connected to by the framework. A 630 * value of true indicates that auto-connection can happen, a value of false indicates that it 631 * cannot. However, even when auto-connection is not possible manual connection by the user is 632 * possible. 633 * 634 * @return the auto-join configuration: true for auto-connection (or join) enabled, false 635 * otherwise. 636 * @hide 637 */ 638 @SystemApi isAutojoinEnabled()639 public boolean isAutojoinEnabled() { 640 return mIsAutojoinEnabled; 641 } 642 643 /** 644 * Indicates whether the user chose this configuration to be treated as metered or not. 645 * 646 * @return One of the values in {@link MeteredOverride} 647 * @hide 648 */ 649 @SystemApi 650 @MeteredOverride getMeteredOverride()651 public int getMeteredOverride() { 652 return mMeteredOverride; 653 } 654 655 /** 656 * Indicates whether a randomized MAC address or device MAC address will be used for 657 * connections to this Passpoint network. If true, a randomized MAC address will be used. 658 * Otherwise, the device MAC address will be used. 659 * 660 * @return true for MAC randomization enabled. False for disabled. 661 * @hide 662 */ 663 @SystemApi isMacRandomizationEnabled()664 public boolean isMacRandomizationEnabled() { 665 return mIsMacRandomizationEnabled; 666 } 667 668 /** 669 * When MAC randomization is enabled, this indicates whether non-persistent MAC randomization or 670 * persistent MAC randomization will be used for connections to this Passpoint network. 671 * If true, the MAC address used for connections will periodically change. Otherwise, the same 672 * locally generated MAC will be used for all connections to this passpoint configuration. 673 * 674 * @return true for enhanced MAC randomization enabled. False for disabled. 675 * @hide 676 */ isNonPersistentMacRandomizationEnabled()677 public boolean isNonPersistentMacRandomizationEnabled() { 678 return mIsNonPersistentMacRandomizationEnabled; 679 } 680 681 /** 682 * Set whether the network is oem paid or not. 683 * @hide 684 */ setOemPaid(boolean isOemPaid)685 public void setOemPaid(boolean isOemPaid) { 686 mIsOemPaid = isOemPaid; 687 } 688 689 /** 690 * Get whether the network is oem paid or not. 691 * @hide 692 */ isOemPaid()693 public boolean isOemPaid() { 694 return mIsOemPaid; 695 } 696 697 /** 698 * Set whether the network is oem private or not. 699 * @hide 700 */ setOemPrivate(boolean isOemPrivate)701 public void setOemPrivate(boolean isOemPrivate) { 702 mIsOemPrivate = isOemPrivate; 703 } 704 705 /** 706 * Get whether the network is oem private or not. 707 * @hide 708 */ isOemPrivate()709 public boolean isOemPrivate() { 710 return mIsOemPrivate; 711 } 712 713 /** 714 * Set whether the network is carrier merged or not. 715 * @hide 716 */ setCarrierMerged(boolean isCarrierMerged)717 public void setCarrierMerged(boolean isCarrierMerged) { 718 mIsCarrierMerged = isCarrierMerged; 719 } 720 721 /** 722 * Get whether the network is carrier merged or not. 723 * @hide 724 */ isCarrierMerged()725 public boolean isCarrierMerged() { 726 return mIsCarrierMerged; 727 } 728 729 /** 730 * Constructor for creating PasspointConfiguration with default values. 731 */ PasspointConfiguration()732 public PasspointConfiguration() {} 733 734 /** 735 * Copy constructor. 736 * 737 * @param source The source to copy from 738 */ PasspointConfiguration(PasspointConfiguration source)739 public PasspointConfiguration(PasspointConfiguration source) { 740 if (source == null) { 741 return; 742 } 743 744 if (source.mHomeSp != null) { 745 mHomeSp = new HomeSp(source.mHomeSp); 746 } 747 if (source.mCredential != null) { 748 mCredential = new Credential(source.mCredential); 749 } 750 if (source.mPolicy != null) { 751 mPolicy = new Policy(source.mPolicy); 752 } 753 if (source.mTrustRootCertList != null) { 754 mTrustRootCertList = Collections.unmodifiableMap(source.mTrustRootCertList); 755 } 756 if (source.mSubscriptionUpdate != null) { 757 mSubscriptionUpdate = new UpdateParameter(source.mSubscriptionUpdate); 758 } 759 mUpdateIdentifier = source.mUpdateIdentifier; 760 mCredentialPriority = source.mCredentialPriority; 761 mSubscriptionCreationTimeInMillis = source.mSubscriptionCreationTimeInMillis; 762 mSubscriptionExpirationTimeMillis = source.mSubscriptionExpirationTimeMillis; 763 mSubscriptionType = source.mSubscriptionType; 764 mUsageLimitDataLimit = source.mUsageLimitDataLimit; 765 mUsageLimitStartTimeInMillis = source.mUsageLimitStartTimeInMillis; 766 mUsageLimitTimeLimitInMinutes = source.mUsageLimitTimeLimitInMinutes; 767 mUsageLimitUsageTimePeriodInMinutes = source.mUsageLimitUsageTimePeriodInMinutes; 768 mServiceFriendlyNames = source.mServiceFriendlyNames; 769 mAaaServerTrustedNames = source.mAaaServerTrustedNames; 770 mCarrierId = source.mCarrierId; 771 mSubscriptionId = source.mSubscriptionId; 772 mIsAutojoinEnabled = source.mIsAutojoinEnabled; 773 mIsMacRandomizationEnabled = source.mIsMacRandomizationEnabled; 774 mIsNonPersistentMacRandomizationEnabled = source.mIsNonPersistentMacRandomizationEnabled; 775 mMeteredOverride = source.mMeteredOverride; 776 mIsCarrierMerged = source.mIsCarrierMerged; 777 mIsOemPaid = source.mIsOemPaid; 778 mIsOemPrivate = source.mIsOemPrivate; 779 mDecoratedIdentityPrefix = source.mDecoratedIdentityPrefix; 780 mSubscriptionGroup = source.mSubscriptionGroup; 781 } 782 783 @Override describeContents()784 public int describeContents() { 785 return 0; 786 } 787 788 @Override writeToParcel(Parcel dest, int flags)789 public void writeToParcel(Parcel dest, int flags) { 790 dest.writeParcelable(mHomeSp, flags); 791 dest.writeParcelable(mCredential, flags); 792 dest.writeParcelable(mPolicy, flags); 793 dest.writeParcelable(mSubscriptionUpdate, flags); 794 writeTrustRootCerts(dest, mTrustRootCertList); 795 dest.writeInt(mUpdateIdentifier); 796 dest.writeInt(mCredentialPriority); 797 dest.writeLong(mSubscriptionCreationTimeInMillis); 798 dest.writeLong(mSubscriptionExpirationTimeMillis); 799 dest.writeString(mSubscriptionType); 800 dest.writeLong(mUsageLimitUsageTimePeriodInMinutes); 801 dest.writeLong(mUsageLimitStartTimeInMillis); 802 dest.writeLong(mUsageLimitDataLimit); 803 dest.writeLong(mUsageLimitTimeLimitInMinutes); 804 dest.writeStringArray(mAaaServerTrustedNames); 805 Bundle bundle = new Bundle(); 806 bundle.putSerializable("serviceFriendlyNames", 807 (HashMap<String, String>) mServiceFriendlyNames); 808 dest.writeBundle(bundle); 809 dest.writeInt(mCarrierId); 810 dest.writeBoolean(mIsAutojoinEnabled); 811 dest.writeBoolean(mIsMacRandomizationEnabled); 812 dest.writeBoolean(mIsNonPersistentMacRandomizationEnabled); 813 dest.writeInt(mMeteredOverride); 814 dest.writeInt(mSubscriptionId); 815 dest.writeBoolean(mIsCarrierMerged); 816 dest.writeBoolean(mIsOemPaid); 817 dest.writeBoolean(mIsOemPrivate); 818 dest.writeString(mDecoratedIdentityPrefix); 819 dest.writeParcelable(mSubscriptionGroup, flags); 820 } 821 822 @Override equals(Object thatObject)823 public boolean equals(Object thatObject) { 824 if (this == thatObject) { 825 return true; 826 } 827 if (!(thatObject instanceof PasspointConfiguration)) { 828 return false; 829 } 830 PasspointConfiguration that = (PasspointConfiguration) thatObject; 831 return (mHomeSp == null ? that.mHomeSp == null : mHomeSp.equals(that.mHomeSp)) 832 && (mAaaServerTrustedNames == null ? that.mAaaServerTrustedNames == null 833 : Arrays.equals(mAaaServerTrustedNames, that.mAaaServerTrustedNames)) 834 && (mCredential == null ? that.mCredential == null 835 : mCredential.equals(that.mCredential)) 836 && (mPolicy == null ? that.mPolicy == null : mPolicy.equals(that.mPolicy)) 837 && (mSubscriptionUpdate == null ? that.mSubscriptionUpdate == null 838 : mSubscriptionUpdate.equals(that.mSubscriptionUpdate)) 839 && isTrustRootCertListEquals(mTrustRootCertList, that.mTrustRootCertList) 840 && mUpdateIdentifier == that.mUpdateIdentifier 841 && mCredentialPriority == that.mCredentialPriority 842 && mSubscriptionCreationTimeInMillis == that.mSubscriptionCreationTimeInMillis 843 && mSubscriptionExpirationTimeMillis == that.mSubscriptionExpirationTimeMillis 844 && TextUtils.equals(mSubscriptionType, that.mSubscriptionType) 845 && mUsageLimitUsageTimePeriodInMinutes == that.mUsageLimitUsageTimePeriodInMinutes 846 && mUsageLimitStartTimeInMillis == that.mUsageLimitStartTimeInMillis 847 && mUsageLimitDataLimit == that.mUsageLimitDataLimit 848 && mUsageLimitTimeLimitInMinutes == that.mUsageLimitTimeLimitInMinutes 849 && mCarrierId == that.mCarrierId 850 && mSubscriptionId == that.mSubscriptionId 851 && mIsOemPrivate == that.mIsOemPrivate 852 && mIsOemPaid == that.mIsOemPaid 853 && mIsCarrierMerged == that.mIsCarrierMerged 854 && mIsAutojoinEnabled == that.mIsAutojoinEnabled 855 && mIsMacRandomizationEnabled == that.mIsMacRandomizationEnabled 856 && mIsNonPersistentMacRandomizationEnabled 857 == that.mIsNonPersistentMacRandomizationEnabled 858 && mMeteredOverride == that.mMeteredOverride 859 && (mServiceFriendlyNames == null ? that.mServiceFriendlyNames == null 860 : mServiceFriendlyNames.equals(that.mServiceFriendlyNames)) 861 && Objects.equals(mDecoratedIdentityPrefix, that.mDecoratedIdentityPrefix) 862 && Objects.equals(mSubscriptionGroup, that.mSubscriptionGroup); 863 } 864 865 @Override hashCode()866 public int hashCode() { 867 return Objects.hash(mHomeSp, mCredential, mPolicy, mSubscriptionUpdate, mTrustRootCertList, 868 mUpdateIdentifier, mCredentialPriority, mSubscriptionCreationTimeInMillis, 869 mSubscriptionExpirationTimeMillis, mUsageLimitUsageTimePeriodInMinutes, 870 mUsageLimitStartTimeInMillis, mUsageLimitDataLimit, mUsageLimitTimeLimitInMinutes, 871 mServiceFriendlyNames, mCarrierId, mIsAutojoinEnabled, mIsMacRandomizationEnabled, 872 mIsNonPersistentMacRandomizationEnabled, mMeteredOverride, mSubscriptionId, 873 mIsCarrierMerged, mIsOemPaid, mIsOemPrivate, mDecoratedIdentityPrefix, 874 mSubscriptionGroup); 875 } 876 877 @Override toString()878 public String toString() { 879 StringBuilder builder = new StringBuilder(); 880 builder.append("UpdateIdentifier: ").append(mUpdateIdentifier).append("\n"); 881 builder.append("CredentialPriority: ").append(mCredentialPriority).append("\n"); 882 builder.append("SubscriptionCreationTime: ").append( 883 mSubscriptionCreationTimeInMillis != Long.MIN_VALUE 884 ? new Date(mSubscriptionCreationTimeInMillis) : "Not specified").append("\n"); 885 builder.append("SubscriptionExpirationTime: ").append( 886 mSubscriptionExpirationTimeMillis != Long.MIN_VALUE 887 ? new Date(mSubscriptionExpirationTimeMillis) : "Not specified").append("\n"); 888 builder.append("UsageLimitStartTime: ").append(mUsageLimitStartTimeInMillis != Long.MIN_VALUE 889 ? new Date(mUsageLimitStartTimeInMillis) : "Not specified").append("\n"); 890 builder.append("UsageTimePeriod: ").append(mUsageLimitUsageTimePeriodInMinutes) 891 .append("\n"); 892 builder.append("UsageLimitDataLimit: ").append(mUsageLimitDataLimit).append("\n"); 893 builder.append("UsageLimitTimeLimit: ").append(mUsageLimitTimeLimitInMinutes).append("\n"); 894 builder.append("Provisioned by a subscription server: ") 895 .append(isOsuProvisioned() ? "Yes" : "No").append("\n"); 896 if (mHomeSp != null) { 897 builder.append("HomeSP Begin ---\n"); 898 builder.append(mHomeSp); 899 builder.append("HomeSP End ---\n"); 900 } 901 if (mCredential != null) { 902 builder.append("Credential Begin ---\n"); 903 builder.append(mCredential); 904 builder.append("Credential End ---\n"); 905 } 906 if (mPolicy != null) { 907 builder.append("Policy Begin ---\n"); 908 builder.append(mPolicy); 909 builder.append("Policy End ---\n"); 910 } 911 if (mSubscriptionUpdate != null) { 912 builder.append("SubscriptionUpdate Begin ---\n"); 913 builder.append(mSubscriptionUpdate); 914 builder.append("SubscriptionUpdate End ---\n"); 915 } 916 if (mTrustRootCertList != null) { 917 builder.append("TrustRootCertServers: ").append(mTrustRootCertList.keySet()) 918 .append("\n"); 919 } 920 if (mAaaServerTrustedNames != null) { 921 builder.append("AAAServerTrustedNames: ") 922 .append(String.join(";", mAaaServerTrustedNames)).append("\n"); 923 } 924 if (mServiceFriendlyNames != null) { 925 builder.append("ServiceFriendlyNames: ").append(mServiceFriendlyNames); 926 } 927 builder.append("CarrierId:" + mCarrierId); 928 builder.append("SubscriptionId:" + mSubscriptionId); 929 builder.append("IsAutojoinEnabled:" + mIsAutojoinEnabled); 930 builder.append("mIsMacRandomizationEnabled:" + mIsMacRandomizationEnabled); 931 builder.append("mIsNonPersistentMacRandomizationEnabled:" 932 + mIsNonPersistentMacRandomizationEnabled); 933 builder.append("mMeteredOverride:" + mMeteredOverride); 934 builder.append("mIsCarrierMerged:" + mIsCarrierMerged); 935 builder.append("mIsOemPaid:" + mIsOemPaid); 936 builder.append("mIsOemPrivate:" + mIsOemPrivate); 937 builder.append("mDecoratedUsernamePrefix:" + mDecoratedIdentityPrefix); 938 builder.append("mSubscriptionGroup:" + mSubscriptionGroup); 939 return builder.toString(); 940 } 941 942 /** 943 * Validate the R1 configuration data. 944 * 945 * @return true on success or false on failure 946 * @hide 947 */ validate()948 public boolean validate() { 949 // Optional: PerProviderSubscription/<X+>/SubscriptionUpdate 950 if (mSubscriptionUpdate != null && !mSubscriptionUpdate.validate()) { 951 return false; 952 } 953 return validateForCommonR1andR2(); 954 } 955 956 /** 957 * Validate the R2 configuration data. 958 * 959 * @return true on success or false on failure 960 * @hide 961 */ validateForR2()962 public boolean validateForR2() { 963 // Required: PerProviderSubscription/UpdateIdentifier 964 if (mUpdateIdentifier == Integer.MIN_VALUE) { 965 return false; 966 } 967 968 // Required: PerProviderSubscription/<X+>/SubscriptionUpdate 969 if (mSubscriptionUpdate == null || !mSubscriptionUpdate.validate()) { 970 return false; 971 } 972 return validateForCommonR1andR2(); 973 } 974 validateForCommonR1andR2()975 private boolean validateForCommonR1andR2() { 976 // Required: PerProviderSubscription/<X+>/HomeSP 977 if (mHomeSp == null || !mHomeSp.validate()) { 978 return false; 979 } 980 981 // Required: PerProviderSubscription/<X+>/Credential 982 if (mCredential == null || !mCredential.validate()) { 983 return false; 984 } 985 986 // Optional: PerProviderSubscription/<X+>/Policy 987 if (mPolicy != null && !mPolicy.validate()) { 988 return false; 989 } 990 // Optional: DecoratedIdentityPrefix 991 if (!TextUtils.isEmpty(mDecoratedIdentityPrefix)) { 992 if (!mDecoratedIdentityPrefix.endsWith("!")) { 993 EventLog.writeEvent(0x534e4554, "246539931", -1, 994 "Invalid decorated identity prefix"); 995 return false; 996 } 997 String[] decoratedIdentityPrefixArray = mDecoratedIdentityPrefix.split("!"); 998 if (decoratedIdentityPrefixArray.length > MAX_NUMBER_OF_ENTRIES) { 999 Log.e(TAG, "too many decoratedIdentityPrefix"); 1000 return false; 1001 } 1002 for (String prefix : decoratedIdentityPrefixArray) { 1003 if (prefix.length() > MAX_STRING_LENGTH) { 1004 Log.e(TAG, "The decoratedIdentityPrefix is too long: " + prefix); 1005 return false; 1006 } 1007 } 1008 } 1009 1010 if (mAaaServerTrustedNames != null) { 1011 if (mAaaServerTrustedNames.length > MAX_NUMBER_OF_ENTRIES) { 1012 Log.e(TAG, "Too many AaaServerTrustedNames"); 1013 return false; 1014 } 1015 for (String fqdn : mAaaServerTrustedNames) { 1016 if (fqdn.getBytes(StandardCharsets.UTF_8).length > MAX_STRING_LENGTH) { 1017 Log.e(TAG, "AaaServerTrustedNames is too long"); 1018 return false; 1019 } 1020 } 1021 } 1022 if (mSubscriptionType != null) { 1023 if (mSubscriptionType.getBytes(StandardCharsets.UTF_8).length > MAX_STRING_LENGTH) { 1024 Log.e(TAG, "SubscriptionType is too long"); 1025 return false; 1026 } 1027 } 1028 1029 if (mTrustRootCertList != null) { 1030 if (mTrustRootCertList.size() > MAX_NUMBER_OF_ENTRIES) { 1031 Log.e(TAG, "Too many TrustRootCert"); 1032 return false; 1033 } 1034 for (Map.Entry<String, byte[]> entry : mTrustRootCertList.entrySet()) { 1035 String url = entry.getKey(); 1036 byte[] certFingerprint = entry.getValue(); 1037 if (TextUtils.isEmpty(url)) { 1038 Log.e(TAG, "Empty URL"); 1039 return false; 1040 } 1041 if (url.getBytes(StandardCharsets.UTF_8).length > MAX_URL_BYTES) { 1042 Log.e(TAG, "URL bytes exceeded the max: " 1043 + url.getBytes(StandardCharsets.UTF_8).length); 1044 return false; 1045 } 1046 1047 if (certFingerprint == null) { 1048 Log.e(TAG, "Fingerprint not specified"); 1049 return false; 1050 } 1051 if (certFingerprint.length != CERTIFICATE_SHA256_BYTES) { 1052 Log.e(TAG, "Incorrect size of trust root certificate SHA-256 fingerprint: " 1053 + certFingerprint.length); 1054 return false; 1055 } 1056 } 1057 } 1058 1059 if (mServiceFriendlyNames != null) { 1060 if (mServiceFriendlyNames.size() > MAX_NUMBER_OF_ENTRIES) { 1061 Log.e(TAG, "ServiceFriendlyNames exceed the max!"); 1062 return false; 1063 } 1064 for (Map.Entry<String, String> names : mServiceFriendlyNames.entrySet()) { 1065 if (names.getKey() == null || names.getValue() == null) { 1066 Log.e(TAG, "Service friendly name entry should not be null"); 1067 return false; 1068 } 1069 if (names.getKey().length() > MAX_STRING_LENGTH 1070 || names.getValue().length() > MAX_STRING_LENGTH) { 1071 Log.e(TAG, "Service friendly name is to long"); 1072 return false; 1073 } 1074 } 1075 } 1076 return true; 1077 } 1078 1079 public static final @android.annotation.NonNull Creator<PasspointConfiguration> CREATOR = 1080 new Creator<PasspointConfiguration>() { 1081 @Override 1082 public PasspointConfiguration createFromParcel(Parcel in) { 1083 PasspointConfiguration config = new PasspointConfiguration(); 1084 config.setHomeSp(in.readParcelable(null)); 1085 config.setCredential(in.readParcelable(null)); 1086 config.setPolicy(in.readParcelable(null)); 1087 config.setSubscriptionUpdate(in.readParcelable(null)); 1088 config.setTrustRootCertList(readTrustRootCerts(in)); 1089 config.setUpdateIdentifier(in.readInt()); 1090 config.setCredentialPriority(in.readInt()); 1091 config.setSubscriptionCreationTimeInMillis(in.readLong()); 1092 config.setSubscriptionExpirationTimeInMillis(in.readLong()); 1093 config.setSubscriptionType(in.readString()); 1094 config.setUsageLimitUsageTimePeriodInMinutes(in.readLong()); 1095 config.setUsageLimitStartTimeInMillis(in.readLong()); 1096 config.setUsageLimitDataLimit(in.readLong()); 1097 config.setUsageLimitTimeLimitInMinutes(in.readLong()); 1098 config.setAaaServerTrustedNames(in.createStringArray()); 1099 Bundle bundle = in.readBundle(); 1100 Map<String, String> friendlyNamesMap = (HashMap) bundle.getSerializable( 1101 "serviceFriendlyNames"); 1102 config.setServiceFriendlyNames(friendlyNamesMap); 1103 config.mCarrierId = in.readInt(); 1104 config.mIsAutojoinEnabled = in.readBoolean(); 1105 config.mIsMacRandomizationEnabled = in.readBoolean(); 1106 config.mIsNonPersistentMacRandomizationEnabled = in.readBoolean(); 1107 config.mMeteredOverride = in.readInt(); 1108 config.mSubscriptionId = in.readInt(); 1109 config.mIsCarrierMerged = in.readBoolean(); 1110 config.mIsOemPaid = in.readBoolean(); 1111 config.mIsOemPrivate = in.readBoolean(); 1112 config.mDecoratedIdentityPrefix = in.readString(); 1113 config.mSubscriptionGroup = in.readParcelable(null); 1114 1115 return config; 1116 } 1117 1118 @Override 1119 public PasspointConfiguration[] newArray(int size) { 1120 return new PasspointConfiguration[size]; 1121 } 1122 1123 /** 1124 * Helper function for reading trust root certificate info list from a Parcel. 1125 * 1126 * @param in The Parcel to read from 1127 * @return The list of trust root certificate URL with the corresponding certificate 1128 * fingerprint 1129 */ 1130 private Map<String, byte[]> readTrustRootCerts(Parcel in) { 1131 int size = in.readInt(); 1132 if (size == NULL_VALUE) { 1133 return null; 1134 } 1135 Map<String, byte[]> trustRootCerts = new HashMap<>(size); 1136 for (int i = 0; i < size; i++) { 1137 String key = in.readString(); 1138 byte[] value = in.createByteArray(); 1139 trustRootCerts.put(key, value); 1140 } 1141 return trustRootCerts; 1142 } 1143 }; 1144 1145 /** 1146 * Helper function for writing trust root certificate information list. 1147 * 1148 * @param dest The Parcel to write to 1149 * @param trustRootCerts The list of trust root certificate URL with the corresponding 1150 * certificate fingerprint 1151 */ writeTrustRootCerts(Parcel dest, Map<String, byte[]> trustRootCerts)1152 private static void writeTrustRootCerts(Parcel dest, Map<String, byte[]> trustRootCerts) { 1153 if (trustRootCerts == null) { 1154 dest.writeInt(NULL_VALUE); 1155 return; 1156 } 1157 dest.writeInt(trustRootCerts.size()); 1158 for (Map.Entry<String, byte[]> entry : trustRootCerts.entrySet()) { 1159 dest.writeString(entry.getKey()); 1160 dest.writeByteArray(entry.getValue()); 1161 } 1162 } 1163 1164 /** 1165 * Helper function for comparing two trust root certificate list. Cannot use Map#equals 1166 * method since the value type (byte[]) doesn't override equals method. 1167 * 1168 * @param list1 The first trust root certificate list 1169 * @param list2 The second trust root certificate list 1170 * @return true if the two list are equal 1171 */ isTrustRootCertListEquals(Map<String, byte[]> list1, Map<String, byte[]> list2)1172 private static boolean isTrustRootCertListEquals(Map<String, byte[]> list1, 1173 Map<String, byte[]> list2) { 1174 if (list1 == null || list2 == null) { 1175 return list1 == list2; 1176 } 1177 if (list1.size() != list2.size()) { 1178 return false; 1179 } 1180 for (Map.Entry<String, byte[]> entry : list1.entrySet()) { 1181 if (!Arrays.equals(entry.getValue(), list2.get(entry.getKey()))) { 1182 return false; 1183 } 1184 } 1185 return true; 1186 } 1187 1188 /** 1189 * Indicates if the Passpoint Configuration was provisioned by a subscription (OSU) server, 1190 * which means that it's an R2 (or R3) profile. 1191 * 1192 * @return true if the Passpoint Configuration was provisioned by a subscription server. 1193 */ isOsuProvisioned()1194 public boolean isOsuProvisioned() { 1195 return getUpdateIdentifier() != Integer.MIN_VALUE; 1196 } 1197 1198 /** 1199 * Get a unique identifier for a PasspointConfiguration object. The identifier depends on the 1200 * configuration that identify the service provider under the HomeSp subtree, and on the 1201 * credential configuration under the Credential subtree. 1202 * The method throws an {@link IllegalStateException} if the configuration under HomeSp subtree 1203 * or the configuration under Credential subtree are not initialized. 1204 * 1205 * @return A unique identifier 1206 */ getUniqueId()1207 public @NonNull String getUniqueId() { 1208 if (mCredential == null || mHomeSp == null || TextUtils.isEmpty(mHomeSp.getFqdn())) { 1209 throw new IllegalStateException("Credential or HomeSP are not initialized"); 1210 } 1211 1212 StringBuilder sb = new StringBuilder(); 1213 sb.append(String.format("%s_%x%x", mHomeSp.getFqdn(), mHomeSp.getUniqueId(), 1214 mCredential.getUniqueId())); 1215 return sb.toString(); 1216 } 1217 1218 /** 1219 * Set a prefix for a decorated identity as per RFC 7542. 1220 * This prefix must contain a list of realms (could be a list of 1) delimited by a '!' 1221 * character. e.g. homerealm.example.org! or proxyrealm.example.net!homerealm.example.org! 1222 * A prefix of "homerealm.example.org!" will generate a decorated identity that 1223 * looks like: homerealm.example.org!user@otherrealm.example.net 1224 * Calling with a null parameter will clear the decorated prefix. 1225 * Note: Caller must verify that the device supports this feature by calling 1226 * {@link WifiManager#isDecoratedIdentitySupported()} 1227 * 1228 * @param decoratedIdentityPrefix The prefix to add to the outer/anonymous identity 1229 */ 1230 @RequiresApi(Build.VERSION_CODES.S) setDecoratedIdentityPrefix(@ullable String decoratedIdentityPrefix)1231 public void setDecoratedIdentityPrefix(@Nullable String decoratedIdentityPrefix) { 1232 if (!SdkLevel.isAtLeastS()) { 1233 throw new UnsupportedOperationException(); 1234 } 1235 if (!TextUtils.isEmpty(decoratedIdentityPrefix) && !decoratedIdentityPrefix.endsWith("!")) { 1236 throw new IllegalArgumentException( 1237 "Decorated identity prefix must be delimited by '!'"); 1238 } 1239 mDecoratedIdentityPrefix = decoratedIdentityPrefix; 1240 } 1241 1242 /** 1243 * Get the decorated identity prefix. 1244 * 1245 * @return The decorated identity prefix 1246 */ 1247 @RequiresApi(Build.VERSION_CODES.S) getDecoratedIdentityPrefix()1248 public @Nullable String getDecoratedIdentityPrefix() { 1249 if (!SdkLevel.isAtLeastS()) { 1250 throw new UnsupportedOperationException(); 1251 } 1252 return mDecoratedIdentityPrefix; 1253 } 1254 } 1255