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