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
17package android.net.wifi;
18
19import android.os.Parcel;
20import android.os.Parcelable;
21
22import java.util.ArrayList;
23import java.util.List;
24
25/**
26 * Describes information about a detected access point. In addition
27 * to the attributes described here, the supplicant keeps track of
28 * {@code quality}, {@code noise}, and {@code maxbitrate} attributes,
29 * but does not currently report them to external clients.
30 */
31public class ScanResult implements Parcelable {
32    /**
33     * The network name.
34     */
35    public String SSID;
36
37    /**
38     * Ascii encoded SSID. This will replace SSID when we deprecate it. @hide
39     */
40    public WifiSsid wifiSsid;
41
42    /**
43     * The address of the access point.
44     */
45    public String BSSID;
46
47    /**
48     * The HESSID from the beacon.
49     * @hide
50     */
51    public long hessid;
52
53    /**
54     * The ANQP Domain ID from the Hotspot 2.0 Indication element, if present.
55     * @hide
56     */
57    public int anqpDomainId;
58
59    /*
60     * This field is equivalent to the |flags|, rather than the |capabilities| field
61     * of the per-BSS scan results returned by WPA supplicant. See the definition of
62     * |struct wpa_bss| in wpa_supplicant/bss.h for more details.
63     */
64    /**
65     * Describes the authentication, key management, and encryption schemes
66     * supported by the access point.
67     */
68    public String capabilities;
69    /**
70     * The detected signal level in dBm, also known as the RSSI.
71     *
72     * <p>Use {@link android.net.wifi.WifiManager#calculateSignalLevel} to convert this number into
73     * an absolute signal level which can be displayed to a user.
74     */
75    public int level;
76    /**
77     * The primary 20 MHz frequency (in MHz) of the channel over which the client is communicating
78     * with the access point.
79     */
80    public int frequency;
81
82   /**
83    * AP Channel bandwidth is 20 MHZ
84    */
85    public static final int CHANNEL_WIDTH_20MHZ = 0;
86   /**
87    * AP Channel bandwidth is 40 MHZ
88    */
89    public static final int CHANNEL_WIDTH_40MHZ = 1;
90   /**
91    * AP Channel bandwidth is 80 MHZ
92    */
93    public static final int CHANNEL_WIDTH_80MHZ = 2;
94   /**
95    * AP Channel bandwidth is 160 MHZ
96    */
97    public static final int CHANNEL_WIDTH_160MHZ = 3;
98   /**
99    * AP Channel bandwidth is 160 MHZ, but 80MHZ + 80MHZ
100    */
101    public static final int CHANNEL_WIDTH_80MHZ_PLUS_MHZ = 4;
102
103   /**
104    * AP Channel bandwidth; one of {@link #CHANNEL_WIDTH_20MHZ}, {@link #CHANNEL_WIDTH_40MHZ},
105    * {@link #CHANNEL_WIDTH_80MHZ}, {@link #CHANNEL_WIDTH_160MHZ}
106    * or {@link #CHANNEL_WIDTH_80MHZ_PLUS_MHZ}.
107    */
108    public int channelWidth;
109
110    /**
111     * Not used if the AP bandwidth is 20 MHz
112     * If the AP use 40, 80 or 160 MHz, this is the center frequency (in MHz)
113     * if the AP use 80 + 80 MHz, this is the center frequency of the first segment (in MHz)
114     */
115    public int centerFreq0;
116
117    /**
118     * Only used if the AP bandwidth is 80 + 80 MHz
119     * if the AP use 80 + 80 MHz, this is the center frequency of the second segment (in MHz)
120     */
121    public int centerFreq1;
122
123    /**
124     * @deprecated use is80211mcResponder() instead
125     * @hide
126     */
127    public boolean is80211McRTTResponder;
128
129    /**
130     * timestamp in microseconds (since boot) when
131     * this result was last seen.
132     */
133    public long timestamp;
134
135    /**
136     * Timestamp representing date when this result was last seen, in milliseconds from 1970
137     * {@hide}
138     */
139    public long seen;
140
141    /**
142     * If the scan result is a valid autojoin candidate
143     * {@hide}
144     */
145    public int isAutoJoinCandidate;
146
147    /**
148     * @hide
149     * Update RSSI of the scan result
150     * @param previousRssi
151     * @param previousSeen
152     * @param maxAge
153     */
154    public void averageRssi(int previousRssi, long previousSeen, int maxAge) {
155
156        if (seen == 0) {
157            seen = System.currentTimeMillis();
158        }
159        long age = seen - previousSeen;
160
161        if (previousSeen > 0 && age > 0 && age < maxAge/2) {
162            // Average the RSSI with previously seen instances of this scan result
163            double alpha = 0.5 - (double) age / (double) maxAge;
164            level = (int) ((double) level * (1 - alpha) + (double) previousRssi * alpha);
165        }
166    }
167
168    /**
169     * num IP configuration failures
170     * @hide
171     */
172    public int numIpConfigFailures;
173
174    /**
175     * @hide
176     * Last time we blacklisted the ScanResult
177     */
178    public long blackListTimestamp;
179
180    /**
181     * Status: indicating the scan result is not a result
182     * that is part of user's saved configurations
183     * @hide
184     */
185    public boolean untrusted;
186
187    /**
188     * Number of time we connected to it
189     * @hide
190     */
191    public int numConnection;
192
193    /**
194     * Number of time autojoin used it
195     * @hide
196     */
197    public int numUsage;
198
199    /**
200     * The approximate distance to the AP in centimeter, if available.  Else
201     * {@link UNSPECIFIED}.
202     * {@hide}
203     */
204    public int distanceCm;
205
206    /**
207     * The standard deviation of the distance to the access point, if available.
208     * Else {@link UNSPECIFIED}.
209     * {@hide}
210     */
211    public int distanceSdCm;
212
213    /** {@hide} */
214    public static final long FLAG_PASSPOINT_NETWORK               = 0x0000000000000001;
215
216    /** {@hide} */
217    public static final long FLAG_80211mc_RESPONDER               = 0x0000000000000002;
218
219    /*
220     * These flags are specific to the ScanResult class, and are not related to the |flags|
221     * field of the per-BSS scan results from WPA supplicant.
222     */
223    /**
224     * Defines flags; such as {@link #FLAG_PASSPOINT_NETWORK}.
225     * {@hide}
226     */
227    public long flags;
228
229    /**
230     * sets a flag in {@link #flags} field
231     * @param flag flag to set
232     * @hide
233     */
234    public void setFlag(long flag) {
235        flags |= flag;
236    }
237
238    /**
239     * clears a flag in {@link #flags} field
240     * @param flag flag to set
241     * @hide
242     */
243    public void clearFlag(long flag) {
244        flags &= ~flag;
245    }
246
247    public boolean is80211mcResponder() {
248        return (flags & FLAG_80211mc_RESPONDER) != 0;
249    }
250
251    public boolean isPasspointNetwork() {
252        return (flags & FLAG_PASSPOINT_NETWORK) != 0;
253    }
254
255    /**
256     * Indicates venue name (such as 'San Francisco Airport') published by access point; only
257     * available on passpoint network and if published by access point.
258     */
259    public CharSequence venueName;
260
261    /**
262     * Indicates passpoint operator name published by access point.
263     */
264    public CharSequence operatorFriendlyName;
265
266    /**
267     * {@hide}
268     */
269    public final static int UNSPECIFIED = -1;
270    /**
271     * @hide
272     */
273    public boolean is24GHz() {
274        return ScanResult.is24GHz(frequency);
275    }
276
277    /**
278     * @hide
279     * TODO: makes real freq boundaries
280     */
281    public static boolean is24GHz(int freq) {
282        return freq > 2400 && freq < 2500;
283    }
284
285    /**
286     * @hide
287     */
288    public boolean is5GHz() {
289        return ScanResult.is5GHz(frequency);
290    }
291
292    /**
293     * @hide
294     * TODO: makes real freq boundaries
295     */
296    public static boolean is5GHz(int freq) {
297        return freq > 4900 && freq < 5900;
298    }
299
300    /**
301     *  @hide
302     * anqp lines from supplicant BSS response
303     */
304    public List<String> anqpLines;
305
306    /**
307     *  @hide
308     * storing the raw bytes of full result IEs
309     **/
310    public byte[] bytes;
311
312    /** information elements from beacon
313     * @hide
314     */
315    public static class InformationElement {
316        public static final int EID_SSID = 0;
317        public static final int EID_SUPPORTED_RATES = 1;
318        public static final int EID_TIM = 5;
319        public static final int EID_BSS_LOAD = 11;
320        public static final int EID_ERP = 42;
321        public static final int EID_RSN = 48;
322        public static final int EID_EXTENDED_SUPPORTED_RATES = 50;
323        public static final int EID_HT_OPERATION = 61;
324        public static final int EID_INTERWORKING = 107;
325        public static final int EID_ROAMING_CONSORTIUM = 111;
326        public static final int EID_EXTENDED_CAPS = 127;
327        public static final int EID_VHT_OPERATION = 192;
328        public static final int EID_VSA = 221;
329
330        public int id;
331        public byte[] bytes;
332
333        public InformationElement() {
334        }
335
336        public InformationElement(InformationElement rhs) {
337            this.id = rhs.id;
338            this.bytes = rhs.bytes.clone();
339        }
340    }
341
342    /** information elements found in the beacon
343     * @hide
344     */
345    public InformationElement[] informationElements;
346
347    /** ANQP response elements.
348     * @hide
349     */
350    public AnqpInformationElement[] anqpElements;
351
352    /** {@hide} */
353    public ScanResult(WifiSsid wifiSsid, String BSSID, long hessid, int anqpDomainId,
354            byte[] osuProviders, String caps, int level, int frequency, long tsf) {
355        this.wifiSsid = wifiSsid;
356        this.SSID = (wifiSsid != null) ? wifiSsid.toString() : WifiSsid.NONE;
357        this.BSSID = BSSID;
358        this.hessid = hessid;
359        this.anqpDomainId = anqpDomainId;
360        if (osuProviders != null) {
361            this.anqpElements = new AnqpInformationElement[1];
362            this.anqpElements[0] =
363                    new AnqpInformationElement(AnqpInformationElement.HOTSPOT20_VENDOR_ID,
364                            AnqpInformationElement.HS_OSU_PROVIDERS, osuProviders);
365        }
366        this.capabilities = caps;
367        this.level = level;
368        this.frequency = frequency;
369        this.timestamp = tsf;
370        this.distanceCm = UNSPECIFIED;
371        this.distanceSdCm = UNSPECIFIED;
372        this.channelWidth = UNSPECIFIED;
373        this.centerFreq0 = UNSPECIFIED;
374        this.centerFreq1 = UNSPECIFIED;
375        this.flags = 0;
376    }
377
378    /** {@hide} */
379    public ScanResult(WifiSsid wifiSsid, String BSSID, String caps, int level, int frequency,
380            long tsf, int distCm, int distSdCm) {
381        this.wifiSsid = wifiSsid;
382        this.SSID = (wifiSsid != null) ? wifiSsid.toString() : WifiSsid.NONE;
383        this.BSSID = BSSID;
384        this.capabilities = caps;
385        this.level = level;
386        this.frequency = frequency;
387        this.timestamp = tsf;
388        this.distanceCm = distCm;
389        this.distanceSdCm = distSdCm;
390        this.channelWidth = UNSPECIFIED;
391        this.centerFreq0 = UNSPECIFIED;
392        this.centerFreq1 = UNSPECIFIED;
393        this.flags = 0;
394    }
395
396    /** {@hide} */
397    public ScanResult(String Ssid, String BSSID, long hessid, int anqpDomainId, String caps,
398            int level, int frequency,
399            long tsf, int distCm, int distSdCm, int channelWidth, int centerFreq0, int centerFreq1,
400            boolean is80211McRTTResponder) {
401        this.SSID = Ssid;
402        this.BSSID = BSSID;
403        this.hessid = hessid;
404        this.anqpDomainId = anqpDomainId;
405        this.capabilities = caps;
406        this.level = level;
407        this.frequency = frequency;
408        this.timestamp = tsf;
409        this.distanceCm = distCm;
410        this.distanceSdCm = distSdCm;
411        this.channelWidth = channelWidth;
412        this.centerFreq0 = centerFreq0;
413        this.centerFreq1 = centerFreq1;
414        if (is80211McRTTResponder) {
415            this.flags = FLAG_80211mc_RESPONDER;
416        } else {
417            this.flags = 0;
418        }
419    }
420
421    /** {@hide} */
422    public ScanResult(WifiSsid wifiSsid, String Ssid, String BSSID, long hessid, int anqpDomainId,
423                  String caps, int level,
424                  int frequency, long tsf, int distCm, int distSdCm, int channelWidth,
425                  int centerFreq0, int centerFreq1, boolean is80211McRTTResponder) {
426        this(Ssid, BSSID, hessid, anqpDomainId, caps, level, frequency, tsf, distCm,
427                distSdCm, channelWidth, centerFreq0, centerFreq1, is80211McRTTResponder);
428        this.wifiSsid = wifiSsid;
429    }
430
431    /** copy constructor {@hide} */
432    public ScanResult(ScanResult source) {
433        if (source != null) {
434            wifiSsid = source.wifiSsid;
435            SSID = source.SSID;
436            BSSID = source.BSSID;
437            hessid = source.hessid;
438            anqpDomainId = source.anqpDomainId;
439            informationElements = source.informationElements;
440            anqpElements = source.anqpElements;
441            capabilities = source.capabilities;
442            level = source.level;
443            frequency = source.frequency;
444            channelWidth = source.channelWidth;
445            centerFreq0 = source.centerFreq0;
446            centerFreq1 = source.centerFreq1;
447            timestamp = source.timestamp;
448            distanceCm = source.distanceCm;
449            distanceSdCm = source.distanceSdCm;
450            seen = source.seen;
451            untrusted = source.untrusted;
452            numConnection = source.numConnection;
453            numUsage = source.numUsage;
454            numIpConfigFailures = source.numIpConfigFailures;
455            isAutoJoinCandidate = source.isAutoJoinCandidate;
456            venueName = source.venueName;
457            operatorFriendlyName = source.operatorFriendlyName;
458            flags = source.flags;
459        }
460    }
461
462    /** empty scan result
463     *
464     * {@hide}
465     * */
466    public ScanResult() {
467    }
468
469    @Override
470    public String toString() {
471        StringBuffer sb = new StringBuffer();
472        String none = "<none>";
473
474        sb.append("SSID: ").
475            append(wifiSsid == null ? WifiSsid.NONE : wifiSsid).
476            append(", BSSID: ").
477            append(BSSID == null ? none : BSSID).
478            append(", capabilities: ").
479            append(capabilities == null ? none : capabilities).
480            append(", level: ").
481            append(level).
482            append(", frequency: ").
483            append(frequency).
484            append(", timestamp: ").
485            append(timestamp);
486
487        sb.append(", distance: ").append((distanceCm != UNSPECIFIED ? distanceCm : "?")).
488                append("(cm)");
489        sb.append(", distanceSd: ").append((distanceSdCm != UNSPECIFIED ? distanceSdCm : "?")).
490                append("(cm)");
491
492        sb.append(", passpoint: ");
493        sb.append(((flags & FLAG_PASSPOINT_NETWORK) != 0) ? "yes" : "no");
494        sb.append(", ChannelBandwidth: ").append(channelWidth);
495        sb.append(", centerFreq0: ").append(centerFreq0);
496        sb.append(", centerFreq1: ").append(centerFreq1);
497        sb.append(", 80211mcResponder: ");
498        sb.append(((flags & FLAG_80211mc_RESPONDER) != 0) ? "is supported" : "is not supported");
499        return sb.toString();
500    }
501
502    /** Implement the Parcelable interface {@hide} */
503    public int describeContents() {
504        return 0;
505    }
506
507    /** Implement the Parcelable interface {@hide} */
508    public void writeToParcel(Parcel dest, int flags) {
509        if (wifiSsid != null) {
510            dest.writeInt(1);
511            wifiSsid.writeToParcel(dest, flags);
512        } else {
513            dest.writeInt(0);
514        }
515        dest.writeString(SSID);
516        dest.writeString(BSSID);
517        dest.writeLong(hessid);
518        dest.writeInt(anqpDomainId);
519        dest.writeString(capabilities);
520        dest.writeInt(level);
521        dest.writeInt(frequency);
522        dest.writeLong(timestamp);
523        dest.writeInt(distanceCm);
524        dest.writeInt(distanceSdCm);
525        dest.writeInt(channelWidth);
526        dest.writeInt(centerFreq0);
527        dest.writeInt(centerFreq1);
528        dest.writeLong(seen);
529        dest.writeInt(untrusted ? 1 : 0);
530        dest.writeInt(numConnection);
531        dest.writeInt(numUsage);
532        dest.writeInt(numIpConfigFailures);
533        dest.writeInt(isAutoJoinCandidate);
534        dest.writeString((venueName != null) ? venueName.toString() : "");
535        dest.writeString((operatorFriendlyName != null) ? operatorFriendlyName.toString() : "");
536        dest.writeLong(this.flags);
537
538        if (informationElements != null) {
539            dest.writeInt(informationElements.length);
540            for (int i = 0; i < informationElements.length; i++) {
541                dest.writeInt(informationElements[i].id);
542                dest.writeInt(informationElements[i].bytes.length);
543                dest.writeByteArray(informationElements[i].bytes);
544            }
545        } else {
546            dest.writeInt(0);
547        }
548
549        if (anqpLines != null) {
550            dest.writeInt(anqpLines.size());
551            for (int i = 0; i < anqpLines.size(); i++) {
552                dest.writeString(anqpLines.get(i));
553            }
554        }
555        else {
556            dest.writeInt(0);
557        }
558        if (anqpElements != null) {
559            dest.writeInt(anqpElements.length);
560            for (AnqpInformationElement element : anqpElements) {
561                dest.writeInt(element.getVendorId());
562                dest.writeInt(element.getElementId());
563                dest.writeInt(element.getPayload().length);
564                dest.writeByteArray(element.getPayload());
565            }
566        } else {
567            dest.writeInt(0);
568        }
569    }
570
571    /** Implement the Parcelable interface {@hide} */
572    public static final Creator<ScanResult> CREATOR =
573        new Creator<ScanResult>() {
574            public ScanResult createFromParcel(Parcel in) {
575                WifiSsid wifiSsid = null;
576                if (in.readInt() == 1) {
577                    wifiSsid = WifiSsid.CREATOR.createFromParcel(in);
578                }
579                ScanResult sr = new ScanResult(
580                        wifiSsid,
581                        in.readString(),                    /* SSID  */
582                        in.readString(),                    /* BSSID */
583                        in.readLong(),                      /* HESSID */
584                        in.readInt(),                       /* ANQP Domain ID */
585                        in.readString(),                    /* capabilities */
586                        in.readInt(),                       /* level */
587                        in.readInt(),                       /* frequency */
588                        in.readLong(),                      /* timestamp */
589                        in.readInt(),                       /* distanceCm */
590                        in.readInt(),                       /* distanceSdCm */
591                        in.readInt(),                       /* channelWidth */
592                        in.readInt(),                       /* centerFreq0 */
593                        in.readInt(),                       /* centerFreq1 */
594                        false                               /* rtt responder,
595                                                               fixed with flags below */
596                );
597
598                sr.seen = in.readLong();
599                sr.untrusted = in.readInt() != 0;
600                sr.numConnection = in.readInt();
601                sr.numUsage = in.readInt();
602                sr.numIpConfigFailures = in.readInt();
603                sr.isAutoJoinCandidate = in.readInt();
604                sr.venueName = in.readString();
605                sr.operatorFriendlyName = in.readString();
606                sr.flags = in.readLong();
607                int n = in.readInt();
608                if (n != 0) {
609                    sr.informationElements = new InformationElement[n];
610                    for (int i = 0; i < n; i++) {
611                        sr.informationElements[i] = new InformationElement();
612                        sr.informationElements[i].id = in.readInt();
613                        int len = in.readInt();
614                        sr.informationElements[i].bytes = new byte[len];
615                        in.readByteArray(sr.informationElements[i].bytes);
616                    }
617                }
618
619                n = in.readInt();
620                if (n != 0) {
621                    sr.anqpLines = new ArrayList<String>();
622                    for (int i = 0; i < n; i++) {
623                        sr.anqpLines.add(in.readString());
624                    }
625                }
626                n = in.readInt();
627                if (n != 0) {
628                    sr.anqpElements = new AnqpInformationElement[n];
629                    for (int i = 0; i < n; i++) {
630                        int vendorId = in.readInt();
631                        int elementId = in.readInt();
632                        int len = in.readInt();
633                        byte[] payload = new byte[len];
634                        in.readByteArray(payload);
635                        sr.anqpElements[i] =
636                                new AnqpInformationElement(vendorId, elementId, payload);
637                    }
638                }
639                return sr;
640            }
641
642            public ScanResult[] newArray(int size) {
643                return new ScanResult[size];
644            }
645        };
646}
647