1 /*
2  * Copyright (C) 2008 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;
18 
19 import android.annotation.NonNull;
20 import android.annotation.SystemApi;
21 import android.compat.annotation.UnsupportedAppUsage;
22 import android.os.Parcel;
23 import android.os.Parcelable;
24 
25 import java.nio.ByteBuffer;
26 import java.util.ArrayList;
27 import java.util.Arrays;
28 import java.util.Collections;
29 import java.util.List;
30 import java.util.Objects;
31 
32 /**
33  * Describes information about a detected access point. In addition
34  * to the attributes described here, the supplicant keeps track of
35  * {@code quality}, {@code noise}, and {@code maxbitrate} attributes,
36  * but does not currently report them to external clients.
37  */
38 public class ScanResult implements Parcelable {
39     /**
40      * The network name.
41      */
42     public String SSID;
43 
44     /**
45      * Ascii encoded SSID. This will replace SSID when we deprecate it. @hide
46      */
47     @UnsupportedAppUsage
48     public WifiSsid wifiSsid;
49 
50     /**
51      * The address of the access point.
52      */
53     public String BSSID;
54 
55     /**
56      * The HESSID from the beacon.
57      * @hide
58      */
59     @UnsupportedAppUsage
60     public long hessid;
61 
62     /**
63      * The ANQP Domain ID from the Hotspot 2.0 Indication element, if present.
64      * @hide
65      */
66     @UnsupportedAppUsage
67     public int anqpDomainId;
68 
69     /*
70      * This field is equivalent to the |flags|, rather than the |capabilities| field
71      * of the per-BSS scan results returned by WPA supplicant. See the definition of
72      * |struct wpa_bss| in wpa_supplicant/bss.h for more details.
73      */
74     /**
75      * Describes the authentication, key management, and encryption schemes
76      * supported by the access point.
77      */
78     public String capabilities;
79 
80     /**
81      * @hide
82      * No security protocol.
83      */
84     public static final int PROTOCOL_NONE = 0;
85     /**
86      * @hide
87      * Security protocol type: WPA version 1.
88      */
89     public static final int PROTOCOL_WPA = 1;
90     /**
91      * @hide
92      * Security protocol type: RSN, for WPA version 2, and version 3.
93      */
94     public static final int PROTOCOL_RSN = 2;
95     /**
96      * @hide
97      * Security protocol type:
98      * OSU Server-only authenticated layer 2 Encryption Network.
99      * Used for Hotspot 2.0.
100      */
101     public static final int PROTOCOL_OSEN = 3;
102 
103     /**
104      * @hide
105      * No security key management scheme.
106      */
107     public static final int KEY_MGMT_NONE = 0;
108     /**
109      * @hide
110      * Security key management scheme: PSK.
111      */
112     public static final int KEY_MGMT_PSK = 1;
113     /**
114      * @hide
115      * Security key management scheme: EAP.
116      */
117     public static final int KEY_MGMT_EAP = 2;
118     /**
119      * @hide
120      * Security key management scheme: FT_PSK.
121      */
122     public static final int KEY_MGMT_FT_PSK = 3;
123     /**
124      * @hide
125      * Security key management scheme: FT_EAP.
126      */
127     public static final int KEY_MGMT_FT_EAP = 4;
128     /**
129      * @hide
130      * Security key management scheme: PSK_SHA256
131      */
132     public static final int KEY_MGMT_PSK_SHA256 = 5;
133     /**
134      * @hide
135      * Security key management scheme: EAP_SHA256.
136      */
137     public static final int KEY_MGMT_EAP_SHA256 = 6;
138     /**
139      * @hide
140      * Security key management scheme: OSEN.
141      * Used for Hotspot 2.0.
142      */
143     public static final int KEY_MGMT_OSEN = 7;
144      /**
145      * @hide
146      * Security key management scheme: SAE.
147      */
148     public static final int KEY_MGMT_SAE = 8;
149     /**
150      * @hide
151      * Security key management scheme: OWE.
152      */
153     public static final int KEY_MGMT_OWE = 9;
154     /**
155      * @hide
156      * Security key management scheme: SUITE_B_192.
157      */
158     public static final int KEY_MGMT_EAP_SUITE_B_192 = 10;
159     /**
160      * @hide
161      * Security key management scheme: FT_SAE.
162      */
163     public static final int KEY_MGMT_FT_SAE = 11;
164     /**
165      * @hide
166      * Security key management scheme: OWE in transition mode.
167      */
168     public static final int KEY_MGMT_OWE_TRANSITION = 12;
169     /**
170      * @hide
171      * No cipher suite.
172      */
173     public static final int CIPHER_NONE = 0;
174     /**
175      * @hide
176      * No group addressed, only used for group data cipher.
177      */
178     public static final int CIPHER_NO_GROUP_ADDRESSED = 1;
179     /**
180      * @hide
181      * Cipher suite: TKIP
182      */
183     public static final int CIPHER_TKIP = 2;
184     /**
185      * @hide
186      * Cipher suite: CCMP
187      */
188     public static final int CIPHER_CCMP = 3;
189     /**
190      * @hide
191      * Cipher suite: GCMP
192      */
193     public static final int CIPHER_GCMP_256 = 4;
194 
195     /**
196      * The detected signal level in dBm, also known as the RSSI.
197      *
198      * <p>Use {@link android.net.wifi.WifiManager#calculateSignalLevel} to convert this number into
199      * an absolute signal level which can be displayed to a user.
200      */
201     public int level;
202     /**
203      * The primary 20 MHz frequency (in MHz) of the channel over which the client is communicating
204      * with the access point.
205      */
206     public int frequency;
207 
208    /**
209     * AP Channel bandwidth is 20 MHZ
210     */
211     public static final int CHANNEL_WIDTH_20MHZ = 0;
212    /**
213     * AP Channel bandwidth is 40 MHZ
214     */
215     public static final int CHANNEL_WIDTH_40MHZ = 1;
216    /**
217     * AP Channel bandwidth is 80 MHZ
218     */
219     public static final int CHANNEL_WIDTH_80MHZ = 2;
220    /**
221     * AP Channel bandwidth is 160 MHZ
222     */
223     public static final int CHANNEL_WIDTH_160MHZ = 3;
224    /**
225     * AP Channel bandwidth is 160 MHZ, but 80MHZ + 80MHZ
226     */
227     public static final int CHANNEL_WIDTH_80MHZ_PLUS_MHZ = 4;
228 
229    /**
230     * AP Channel bandwidth; one of {@link #CHANNEL_WIDTH_20MHZ}, {@link #CHANNEL_WIDTH_40MHZ},
231     * {@link #CHANNEL_WIDTH_80MHZ}, {@link #CHANNEL_WIDTH_160MHZ}
232     * or {@link #CHANNEL_WIDTH_80MHZ_PLUS_MHZ}.
233     */
234     public int channelWidth;
235 
236     /**
237      * Not used if the AP bandwidth is 20 MHz
238      * If the AP use 40, 80 or 160 MHz, this is the center frequency (in MHz)
239      * if the AP use 80 + 80 MHz, this is the center frequency of the first segment (in MHz)
240      */
241     public int centerFreq0;
242 
243     /**
244      * Only used if the AP bandwidth is 80 + 80 MHz
245      * if the AP use 80 + 80 MHz, this is the center frequency of the second segment (in MHz)
246      */
247     public int centerFreq1;
248 
249     /**
250      * @deprecated use is80211mcResponder() instead
251      * @hide
252      */
253     @UnsupportedAppUsage
254     public boolean is80211McRTTResponder;
255 
256     /**
257      * timestamp in microseconds (since boot) when
258      * this result was last seen.
259      */
260     public long timestamp;
261 
262     /**
263      * Timestamp representing date when this result was last seen, in milliseconds from 1970
264      * {@hide}
265      */
266     @UnsupportedAppUsage
267     public long seen;
268 
269     /**
270      * On devices with multiple hardware radio chains, this class provides metadata about
271      * each radio chain that was used to receive this scan result (probe response or beacon).
272      * {@hide}
273      */
274     public static class RadioChainInfo {
275         /** Vendor defined id for a radio chain. */
276         public int id;
277         /** Detected signal level in dBm (also known as the RSSI) on this radio chain. */
278         public int level;
279 
280         @Override
toString()281         public String toString() {
282             return "RadioChainInfo: id=" + id + ", level=" + level;
283         }
284 
285         @Override
equals(Object otherObj)286         public boolean equals(Object otherObj) {
287             if (this == otherObj) {
288                 return true;
289             }
290             if (!(otherObj instanceof RadioChainInfo)) {
291                 return false;
292             }
293             RadioChainInfo other = (RadioChainInfo) otherObj;
294             return id == other.id && level == other.level;
295         }
296 
297         @Override
hashCode()298         public int hashCode() {
299             return Objects.hash(id, level);
300         }
301     };
302 
303     /**
304      * Information about the list of the radio chains used to receive this scan result
305      * (probe response or beacon).
306      *
307      * For Example: On devices with 2 hardware radio chains, this list could hold 1 or 2
308      * entries based on whether this scan result was received using one or both the chains.
309      * {@hide}
310      */
311     public RadioChainInfo[] radioChainInfos;
312 
313     /**
314      * Status indicating the scan result does not correspond to a user's saved configuration
315      * @hide
316      * @removed
317      */
318     @SystemApi
319     public boolean untrusted;
320 
321     /**
322      * Number of time autojoin used it
323      * @hide
324      */
325     @UnsupportedAppUsage
326     public int numUsage;
327 
328     /**
329      * The approximate distance to the AP in centimeter, if available.  Else
330      * {@link UNSPECIFIED}.
331      * {@hide}
332      */
333     @UnsupportedAppUsage
334     public int distanceCm;
335 
336     /**
337      * The standard deviation of the distance to the access point, if available.
338      * Else {@link UNSPECIFIED}.
339      * {@hide}
340      */
341     @UnsupportedAppUsage
342     public int distanceSdCm;
343 
344     /** {@hide} */
345     public static final long FLAG_PASSPOINT_NETWORK               = 0x0000000000000001;
346 
347     /** {@hide} */
348     public static final long FLAG_80211mc_RESPONDER               = 0x0000000000000002;
349 
350     /*
351      * These flags are specific to the ScanResult class, and are not related to the |flags|
352      * field of the per-BSS scan results from WPA supplicant.
353      */
354     /**
355      * Defines flags; such as {@link #FLAG_PASSPOINT_NETWORK}.
356      * {@hide}
357      */
358     @UnsupportedAppUsage
359     public long flags;
360 
361     /**
362      * sets a flag in {@link #flags} field
363      * @param flag flag to set
364      * @hide
365      */
setFlag(long flag)366     public void setFlag(long flag) {
367         flags |= flag;
368     }
369 
370     /**
371      * clears a flag in {@link #flags} field
372      * @param flag flag to set
373      * @hide
374      */
clearFlag(long flag)375     public void clearFlag(long flag) {
376         flags &= ~flag;
377     }
378 
is80211mcResponder()379     public boolean is80211mcResponder() {
380         return (flags & FLAG_80211mc_RESPONDER) != 0;
381     }
382 
isPasspointNetwork()383     public boolean isPasspointNetwork() {
384         return (flags & FLAG_PASSPOINT_NETWORK) != 0;
385     }
386 
387     /**
388      * Indicates venue name (such as 'San Francisco Airport') published by access point; only
389      * available on Passpoint network and if published by access point.
390      */
391     public CharSequence venueName;
392 
393     /**
394      * Indicates Passpoint operator name published by access point.
395      */
396     public CharSequence operatorFriendlyName;
397 
398     /**
399      * {@hide}
400      */
401     public final static int UNSPECIFIED = -1;
402     /**
403      * @hide
404      */
is24GHz()405     public boolean is24GHz() {
406         return ScanResult.is24GHz(frequency);
407     }
408 
409     /**
410      * @hide
411      * TODO: makes real freq boundaries
412      */
is24GHz(int freq)413     public static boolean is24GHz(int freq) {
414         return freq > 2400 && freq < 2500;
415     }
416 
417     /**
418      * @hide
419      */
is5GHz()420     public boolean is5GHz() {
421         return ScanResult.is5GHz(frequency);
422     }
423 
424     /**
425      * @hide
426      * TODO: makes real freq boundaries
427      */
is5GHz(int freq)428     public static boolean is5GHz(int freq) {
429         return freq > 4900 && freq < 5900;
430     }
431 
432     /**
433      *  @hide
434      * anqp lines from supplicant BSS response
435      */
436     @UnsupportedAppUsage
437     public List<String> anqpLines;
438 
439     /**
440      * information elements from beacon.
441      */
442     public static class InformationElement {
443         /** @hide */
444         @UnsupportedAppUsage
445         public static final int EID_SSID = 0;
446         /** @hide */
447         @UnsupportedAppUsage
448         public static final int EID_SUPPORTED_RATES = 1;
449         /** @hide */
450         @UnsupportedAppUsage
451         public static final int EID_TIM = 5;
452         /** @hide */
453         @UnsupportedAppUsage
454         public static final int EID_BSS_LOAD = 11;
455         /** @hide */
456         @UnsupportedAppUsage
457         public static final int EID_ERP = 42;
458         /** @hide */
459         public static final int EID_HT_CAPABILITIES = 45;
460         /** @hide */
461         @UnsupportedAppUsage
462         public static final int EID_RSN = 48;
463         /** @hide */
464         @UnsupportedAppUsage
465         public static final int EID_EXTENDED_SUPPORTED_RATES = 50;
466         /** @hide */
467         @UnsupportedAppUsage
468         public static final int EID_HT_OPERATION = 61;
469         /** @hide */
470         @UnsupportedAppUsage
471         public static final int EID_INTERWORKING = 107;
472         /** @hide */
473         @UnsupportedAppUsage
474         public static final int EID_ROAMING_CONSORTIUM = 111;
475         /** @hide */
476         @UnsupportedAppUsage
477         public static final int EID_EXTENDED_CAPS = 127;
478         /** @hide */
479         public static final int EID_VHT_CAPABILITIES = 191;
480         /** @hide */
481         @UnsupportedAppUsage
482         public static final int EID_VHT_OPERATION = 192;
483         /** @hide */
484         @UnsupportedAppUsage
485         public static final int EID_VSA = 221;
486 
487         /** @hide */
488         @UnsupportedAppUsage
489         public int id;
490         /** @hide */
491         @UnsupportedAppUsage
492         public byte[] bytes;
493 
494         /** @hide */
InformationElement()495         public InformationElement() {
496         }
497 
InformationElement(@onNull InformationElement rhs)498         public InformationElement(@NonNull InformationElement rhs) {
499             this.id = rhs.id;
500             this.bytes = rhs.bytes.clone();
501         }
502 
503         /**
504          * The element ID of the information element. Defined in the IEEE 802.11-2016 spec
505          * Table 9-77.
506          */
getId()507         public int getId() {
508             return id;
509         }
510 
511         /**
512          * Get the specific content of the information element.
513          */
514         @NonNull
getBytes()515         public ByteBuffer getBytes() {
516             return ByteBuffer.wrap(bytes).asReadOnlyBuffer();
517         }
518     }
519 
520     /**
521      * information elements found in the beacon.
522      * @hide
523      */
524     @UnsupportedAppUsage
525     public InformationElement[] informationElements;
526     /**
527      * Get all information elements found in the beacon.
528      */
529     @NonNull
getInformationElements()530     public List<InformationElement> getInformationElements() {
531         return Collections.unmodifiableList(Arrays.asList(informationElements));
532     }
533 
534     /** ANQP response elements.
535      * @hide
536      */
537     public AnqpInformationElement[] anqpElements;
538 
539     /**
540      * Flag indicating if this AP is a carrier AP. The determination is based
541      * on the AP's SSID and if AP is using EAP security.
542      *
543      * @hide
544      */
545     public boolean isCarrierAp;
546 
547     /**
548      * The EAP type {@link WifiEnterpriseConfig.Eap} associated with this AP if it is a carrier AP.
549      *
550      * @hide
551      */
552     public int carrierApEapType;
553 
554     /**
555      * The name of the carrier that's associated with this AP if it is a carrier AP.
556      *
557      * @hide
558      */
559     public String carrierName;
560 
561     /** {@hide} */
ScanResult(WifiSsid wifiSsid, String BSSID, long hessid, int anqpDomainId, byte[] osuProviders, String caps, int level, int frequency, long tsf)562     public ScanResult(WifiSsid wifiSsid, String BSSID, long hessid, int anqpDomainId,
563             byte[] osuProviders, String caps, int level, int frequency, long tsf) {
564         this.wifiSsid = wifiSsid;
565         this.SSID = (wifiSsid != null) ? wifiSsid.toString() : WifiSsid.NONE;
566         this.BSSID = BSSID;
567         this.hessid = hessid;
568         this.anqpDomainId = anqpDomainId;
569         if (osuProviders != null) {
570             this.anqpElements = new AnqpInformationElement[1];
571             this.anqpElements[0] =
572                     new AnqpInformationElement(AnqpInformationElement.HOTSPOT20_VENDOR_ID,
573                             AnqpInformationElement.HS_OSU_PROVIDERS, osuProviders);
574         }
575         this.capabilities = caps;
576         this.level = level;
577         this.frequency = frequency;
578         this.timestamp = tsf;
579         this.distanceCm = UNSPECIFIED;
580         this.distanceSdCm = UNSPECIFIED;
581         this.channelWidth = UNSPECIFIED;
582         this.centerFreq0 = UNSPECIFIED;
583         this.centerFreq1 = UNSPECIFIED;
584         this.flags = 0;
585         this.isCarrierAp = false;
586         this.carrierApEapType = UNSPECIFIED;
587         this.carrierName = null;
588         this.radioChainInfos = null;
589     }
590 
591     /** {@hide} */
ScanResult(WifiSsid wifiSsid, String BSSID, String caps, int level, int frequency, long tsf, int distCm, int distSdCm)592     public ScanResult(WifiSsid wifiSsid, String BSSID, String caps, int level, int frequency,
593             long tsf, int distCm, int distSdCm) {
594         this.wifiSsid = wifiSsid;
595         this.SSID = (wifiSsid != null) ? wifiSsid.toString() : WifiSsid.NONE;
596         this.BSSID = BSSID;
597         this.capabilities = caps;
598         this.level = level;
599         this.frequency = frequency;
600         this.timestamp = tsf;
601         this.distanceCm = distCm;
602         this.distanceSdCm = distSdCm;
603         this.channelWidth = UNSPECIFIED;
604         this.centerFreq0 = UNSPECIFIED;
605         this.centerFreq1 = UNSPECIFIED;
606         this.flags = 0;
607         this.isCarrierAp = false;
608         this.carrierApEapType = UNSPECIFIED;
609         this.carrierName = null;
610         this.radioChainInfos = null;
611     }
612 
613     /** {@hide} */
ScanResult(String Ssid, String BSSID, long hessid, int anqpDomainId, String caps, int level, int frequency, long tsf, int distCm, int distSdCm, int channelWidth, int centerFreq0, int centerFreq1, boolean is80211McRTTResponder)614     public ScanResult(String Ssid, String BSSID, long hessid, int anqpDomainId, String caps,
615             int level, int frequency,
616             long tsf, int distCm, int distSdCm, int channelWidth, int centerFreq0, int centerFreq1,
617             boolean is80211McRTTResponder) {
618         this.SSID = Ssid;
619         this.BSSID = BSSID;
620         this.hessid = hessid;
621         this.anqpDomainId = anqpDomainId;
622         this.capabilities = caps;
623         this.level = level;
624         this.frequency = frequency;
625         this.timestamp = tsf;
626         this.distanceCm = distCm;
627         this.distanceSdCm = distSdCm;
628         this.channelWidth = channelWidth;
629         this.centerFreq0 = centerFreq0;
630         this.centerFreq1 = centerFreq1;
631         if (is80211McRTTResponder) {
632             this.flags = FLAG_80211mc_RESPONDER;
633         } else {
634             this.flags = 0;
635         }
636         this.isCarrierAp = false;
637         this.carrierApEapType = UNSPECIFIED;
638         this.carrierName = null;
639         this.radioChainInfos = null;
640     }
641 
642     /** {@hide} */
ScanResult(WifiSsid wifiSsid, String Ssid, String BSSID, long hessid, int anqpDomainId, String caps, int level, int frequency, long tsf, int distCm, int distSdCm, int channelWidth, int centerFreq0, int centerFreq1, boolean is80211McRTTResponder)643     public ScanResult(WifiSsid wifiSsid, String Ssid, String BSSID, long hessid, int anqpDomainId,
644                   String caps, int level,
645                   int frequency, long tsf, int distCm, int distSdCm, int channelWidth,
646                   int centerFreq0, int centerFreq1, boolean is80211McRTTResponder) {
647         this(Ssid, BSSID, hessid, anqpDomainId, caps, level, frequency, tsf, distCm,
648                 distSdCm, channelWidth, centerFreq0, centerFreq1, is80211McRTTResponder);
649         this.wifiSsid = wifiSsid;
650     }
651 
652     /** copy constructor */
ScanResult(@onNull ScanResult source)653     public ScanResult(@NonNull ScanResult source) {
654         if (source != null) {
655             wifiSsid = source.wifiSsid;
656             SSID = source.SSID;
657             BSSID = source.BSSID;
658             hessid = source.hessid;
659             anqpDomainId = source.anqpDomainId;
660             informationElements = source.informationElements;
661             anqpElements = source.anqpElements;
662             capabilities = source.capabilities;
663             level = source.level;
664             frequency = source.frequency;
665             channelWidth = source.channelWidth;
666             centerFreq0 = source.centerFreq0;
667             centerFreq1 = source.centerFreq1;
668             timestamp = source.timestamp;
669             distanceCm = source.distanceCm;
670             distanceSdCm = source.distanceSdCm;
671             seen = source.seen;
672             untrusted = source.untrusted;
673             numUsage = source.numUsage;
674             venueName = source.venueName;
675             operatorFriendlyName = source.operatorFriendlyName;
676             flags = source.flags;
677             isCarrierAp = source.isCarrierAp;
678             carrierApEapType = source.carrierApEapType;
679             carrierName = source.carrierName;
680             radioChainInfos = source.radioChainInfos;
681         }
682     }
683 
684     /** empty scan result
685      *
686      * {@hide}
687      * */
ScanResult()688     public ScanResult() {
689     }
690 
691     @Override
toString()692     public String toString() {
693         StringBuffer sb = new StringBuffer();
694         String none = "<none>";
695 
696         sb.append("SSID: ").
697             append(wifiSsid == null ? WifiSsid.NONE : wifiSsid).
698             append(", BSSID: ").
699             append(BSSID == null ? none : BSSID).
700             append(", capabilities: ").
701             append(capabilities == null ? none : capabilities).
702             append(", level: ").
703             append(level).
704             append(", frequency: ").
705             append(frequency).
706             append(", timestamp: ").
707             append(timestamp);
708 
709         sb.append(", distance: ").append((distanceCm != UNSPECIFIED ? distanceCm : "?")).
710                 append("(cm)");
711         sb.append(", distanceSd: ").append((distanceSdCm != UNSPECIFIED ? distanceSdCm : "?")).
712                 append("(cm)");
713 
714         sb.append(", passpoint: ");
715         sb.append(((flags & FLAG_PASSPOINT_NETWORK) != 0) ? "yes" : "no");
716         sb.append(", ChannelBandwidth: ").append(channelWidth);
717         sb.append(", centerFreq0: ").append(centerFreq0);
718         sb.append(", centerFreq1: ").append(centerFreq1);
719         sb.append(", 80211mcResponder: ");
720         sb.append(((flags & FLAG_80211mc_RESPONDER) != 0) ? "is supported" : "is not supported");
721         sb.append(", Carrier AP: ").append(isCarrierAp ? "yes" : "no");
722         sb.append(", Carrier AP EAP Type: ").append(carrierApEapType);
723         sb.append(", Carrier name: ").append(carrierName);
724         sb.append(", Radio Chain Infos: ").append(Arrays.toString(radioChainInfos));
725         return sb.toString();
726     }
727 
728     /** Implement the Parcelable interface {@hide} */
describeContents()729     public int describeContents() {
730         return 0;
731     }
732 
733     /** Implement the Parcelable interface {@hide} */
writeToParcel(Parcel dest, int flags)734     public void writeToParcel(Parcel dest, int flags) {
735         if (wifiSsid != null) {
736             dest.writeInt(1);
737             wifiSsid.writeToParcel(dest, flags);
738         } else {
739             dest.writeInt(0);
740         }
741         dest.writeString(SSID);
742         dest.writeString(BSSID);
743         dest.writeLong(hessid);
744         dest.writeInt(anqpDomainId);
745         dest.writeString(capabilities);
746         dest.writeInt(level);
747         dest.writeInt(frequency);
748         dest.writeLong(timestamp);
749         dest.writeInt(distanceCm);
750         dest.writeInt(distanceSdCm);
751         dest.writeInt(channelWidth);
752         dest.writeInt(centerFreq0);
753         dest.writeInt(centerFreq1);
754         dest.writeLong(seen);
755         dest.writeInt(untrusted ? 1 : 0);
756         dest.writeInt(numUsage);
757         dest.writeString((venueName != null) ? venueName.toString() : "");
758         dest.writeString((operatorFriendlyName != null) ? operatorFriendlyName.toString() : "");
759         dest.writeLong(this.flags);
760 
761         if (informationElements != null) {
762             dest.writeInt(informationElements.length);
763             for (int i = 0; i < informationElements.length; i++) {
764                 dest.writeInt(informationElements[i].id);
765                 dest.writeInt(informationElements[i].bytes.length);
766                 dest.writeByteArray(informationElements[i].bytes);
767             }
768         } else {
769             dest.writeInt(0);
770         }
771 
772         if (anqpLines != null) {
773             dest.writeInt(anqpLines.size());
774             for (int i = 0; i < anqpLines.size(); i++) {
775                 dest.writeString(anqpLines.get(i));
776             }
777         }
778         else {
779             dest.writeInt(0);
780         }
781         if (anqpElements != null) {
782             dest.writeInt(anqpElements.length);
783             for (AnqpInformationElement element : anqpElements) {
784                 dest.writeInt(element.getVendorId());
785                 dest.writeInt(element.getElementId());
786                 dest.writeInt(element.getPayload().length);
787                 dest.writeByteArray(element.getPayload());
788             }
789         } else {
790             dest.writeInt(0);
791         }
792         dest.writeInt(isCarrierAp ? 1 : 0);
793         dest.writeInt(carrierApEapType);
794         dest.writeString(carrierName);
795 
796         if (radioChainInfos != null) {
797             dest.writeInt(radioChainInfos.length);
798             for (int i = 0; i < radioChainInfos.length; i++) {
799                 dest.writeInt(radioChainInfos[i].id);
800                 dest.writeInt(radioChainInfos[i].level);
801             }
802         } else {
803             dest.writeInt(0);
804         }
805     }
806 
807     /** Implement the Parcelable interface */
808     public static final @NonNull Creator<ScanResult> CREATOR =
809         new Creator<ScanResult>() {
810             public ScanResult createFromParcel(Parcel in) {
811                 WifiSsid wifiSsid = null;
812                 if (in.readInt() == 1) {
813                     wifiSsid = WifiSsid.CREATOR.createFromParcel(in);
814                 }
815                 ScanResult sr = new ScanResult(
816                         wifiSsid,
817                         in.readString(),                    /* SSID  */
818                         in.readString(),                    /* BSSID */
819                         in.readLong(),                      /* HESSID */
820                         in.readInt(),                       /* ANQP Domain ID */
821                         in.readString(),                    /* capabilities */
822                         in.readInt(),                       /* level */
823                         in.readInt(),                       /* frequency */
824                         in.readLong(),                      /* timestamp */
825                         in.readInt(),                       /* distanceCm */
826                         in.readInt(),                       /* distanceSdCm */
827                         in.readInt(),                       /* channelWidth */
828                         in.readInt(),                       /* centerFreq0 */
829                         in.readInt(),                       /* centerFreq1 */
830                         false                               /* rtt responder,
831                                                                fixed with flags below */
832                 );
833 
834                 sr.seen = in.readLong();
835                 sr.untrusted = in.readInt() != 0;
836                 sr.numUsage = in.readInt();
837                 sr.venueName = in.readString();
838                 sr.operatorFriendlyName = in.readString();
839                 sr.flags = in.readLong();
840                 int n = in.readInt();
841                 if (n != 0) {
842                     sr.informationElements = new InformationElement[n];
843                     for (int i = 0; i < n; i++) {
844                         sr.informationElements[i] = new InformationElement();
845                         sr.informationElements[i].id = in.readInt();
846                         int len = in.readInt();
847                         sr.informationElements[i].bytes = new byte[len];
848                         in.readByteArray(sr.informationElements[i].bytes);
849                     }
850                 }
851 
852                 n = in.readInt();
853                 if (n != 0) {
854                     sr.anqpLines = new ArrayList<String>();
855                     for (int i = 0; i < n; i++) {
856                         sr.anqpLines.add(in.readString());
857                     }
858                 }
859                 n = in.readInt();
860                 if (n != 0) {
861                     sr.anqpElements = new AnqpInformationElement[n];
862                     for (int i = 0; i < n; i++) {
863                         int vendorId = in.readInt();
864                         int elementId = in.readInt();
865                         int len = in.readInt();
866                         byte[] payload = new byte[len];
867                         in.readByteArray(payload);
868                         sr.anqpElements[i] =
869                                 new AnqpInformationElement(vendorId, elementId, payload);
870                     }
871                 }
872                 sr.isCarrierAp = in.readInt() != 0;
873                 sr.carrierApEapType = in.readInt();
874                 sr.carrierName = in.readString();
875                 n = in.readInt();
876                 if (n != 0) {
877                     sr.radioChainInfos = new RadioChainInfo[n];
878                     for (int i = 0; i < n; i++) {
879                         sr.radioChainInfos[i] = new RadioChainInfo();
880                         sr.radioChainInfos[i].id = in.readInt();
881                         sr.radioChainInfos[i].level = in.readInt();
882                     }
883                 }
884                 return sr;
885             }
886 
887             public ScanResult[] newArray(int size) {
888                 return new ScanResult[size];
889             }
890         };
891 }
892