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.Manifest;
20 import android.annotation.RequiresPermission;
21 import android.annotation.SuppressLint;
22 import android.annotation.SystemApi;
23 import android.annotation.SystemService;
24 import android.content.Context;
25 import android.os.Bundle;
26 import android.os.Handler;
27 import android.os.Looper;
28 import android.os.Message;
29 import android.os.Messenger;
30 import android.os.Parcel;
31 import android.os.Parcelable;
32 import android.os.RemoteException;
33 import android.os.WorkSource;
34 import android.util.Log;
35 import android.util.SparseArray;
36 
37 import com.android.internal.util.AsyncChannel;
38 import com.android.internal.util.Preconditions;
39 import com.android.internal.util.Protocol;
40 
41 import java.util.ArrayList;
42 import java.util.Arrays;
43 import java.util.List;
44 
45 /**
46  * This class provides a way to scan the Wifi universe around the device
47  * @hide
48  */
49 @SystemApi
50 @SystemService(Context.WIFI_SCANNING_SERVICE)
51 public class WifiScanner {
52 
53     /** no band specified; use channel list instead */
54     public static final int WIFI_BAND_UNSPECIFIED = 0;      /* not specified */
55 
56     /** 2.4 GHz band */
57     public static final int WIFI_BAND_24_GHZ = 1;           /* 2.4 GHz band */
58     /** 5 GHz band excluding DFS channels */
59     public static final int WIFI_BAND_5_GHZ = 2;            /* 5 GHz band without DFS channels */
60     /** DFS channels from 5 GHz band only */
61     public static final int WIFI_BAND_5_GHZ_DFS_ONLY  = 4;  /* 5 GHz band with DFS channels */
62     /** 5 GHz band including DFS channels */
63     public static final int WIFI_BAND_5_GHZ_WITH_DFS  = 6;  /* 5 GHz band with DFS channels */
64     /** Both 2.4 GHz band and 5 GHz band; no DFS channels */
65     public static final int WIFI_BAND_BOTH = 3;             /* both bands without DFS channels */
66     /** Both 2.4 GHz band and 5 GHz band; with DFS channels */
67     public static final int WIFI_BAND_BOTH_WITH_DFS = 7;    /* both bands with DFS channels */
68 
69     /** Minimum supported scanning period */
70     public static final int MIN_SCAN_PERIOD_MS = 1000;      /* minimum supported period */
71     /** Maximum supported scanning period */
72     public static final int MAX_SCAN_PERIOD_MS = 1024000;   /* maximum supported period */
73 
74     /** No Error */
75     public static final int REASON_SUCCEEDED = 0;
76     /** Unknown error */
77     public static final int REASON_UNSPECIFIED = -1;
78     /** Invalid listener */
79     public static final int REASON_INVALID_LISTENER = -2;
80     /** Invalid request */
81     public static final int REASON_INVALID_REQUEST = -3;
82     /** Invalid request */
83     public static final int REASON_NOT_AUTHORIZED = -4;
84     /** An outstanding request with the same listener hasn't finished yet. */
85     public static final int REASON_DUPLICATE_REQEUST = -5;
86 
87     /** @hide */
88     public static final String GET_AVAILABLE_CHANNELS_EXTRA = "Channels";
89 
90     /**
91      * Generic action callback invocation interface
92      *  @hide
93      */
94     @SystemApi
95     public static interface ActionListener {
onSuccess()96         public void onSuccess();
onFailure(int reason, String description)97         public void onFailure(int reason, String description);
98     }
99 
100     /**
101      * gives you all the possible channels; channel is specified as an
102      * integer with frequency in MHz i.e. channel 1 is 2412
103      * @hide
104      */
getAvailableChannels(int band)105     public List<Integer> getAvailableChannels(int band) {
106         try {
107             Bundle bundle =  mService.getAvailableChannels(band);
108             return bundle.getIntegerArrayList(GET_AVAILABLE_CHANNELS_EXTRA);
109         } catch (RemoteException e) {
110             return null;
111         }
112     }
113 
114     /**
115      * provides channel specification for scanning
116      */
117     public static class ChannelSpec {
118         /**
119          * channel frequency in MHz; for example channel 1 is specified as 2412
120          */
121         public int frequency;
122         /**
123          * if true, scan this channel in passive fashion.
124          * This flag is ignored on DFS channel specification.
125          * @hide
126          */
127         public boolean passive;                                    /* ignored on DFS channels */
128         /**
129          * how long to dwell on this channel
130          * @hide
131          */
132         public int dwellTimeMS;                                    /* not supported for now */
133 
134         /**
135          * default constructor for channel spec
136          */
ChannelSpec(int frequency)137         public ChannelSpec(int frequency) {
138             this.frequency = frequency;
139             passive = false;
140             dwellTimeMS = 0;
141         }
142     }
143 
144     /**
145      * reports {@link ScanListener#onResults} when underlying buffers are full
146      * this is simply the lack of the {@link #REPORT_EVENT_AFTER_EACH_SCAN} flag
147      * @deprecated It is not supported anymore.
148      */
149     @Deprecated
150     public static final int REPORT_EVENT_AFTER_BUFFER_FULL = 0;
151     /**
152      * reports {@link ScanListener#onResults} after each scan
153      */
154     public static final int REPORT_EVENT_AFTER_EACH_SCAN = (1 << 0);
155     /**
156      * reports {@link ScanListener#onFullResult} whenever each beacon is discovered
157      */
158     public static final int REPORT_EVENT_FULL_SCAN_RESULT = (1 << 1);
159     /**
160      * Do not place scans in the chip's scan history buffer
161      */
162     public static final int REPORT_EVENT_NO_BATCH = (1 << 2);
163 
164     /**
165      * This is used to indicate the purpose of the scan to the wifi chip in
166      * {@link ScanSettings#type}.
167      * On devices with multiple hardware radio chains (and hence different modes of scan),
168      * this type serves as an indication to the hardware on what mode of scan to perform.
169      * Only apps holding android.Manifest.permission.NETWORK_STACK permission can set this value.
170      *
171      * Note: This serves as an intent and not as a stipulation, the wifi chip
172      * might honor or ignore the indication based on the current radio conditions. Always
173      * use the {@link ScanResult#radioChainInfos} to figure out the radio chain configuration used
174      * to receive the corresponding scan result.
175      */
176     /** {@hide} */
177     public static final int TYPE_LOW_LATENCY = 0;
178     /** {@hide} */
179     public static final int TYPE_LOW_POWER = 1;
180     /** {@hide} */
181     public static final int TYPE_HIGH_ACCURACY = 2;
182 
183     /** {@hide} */
184     public static final String SCAN_PARAMS_SCAN_SETTINGS_KEY = "ScanSettings";
185     /** {@hide} */
186     public static final String SCAN_PARAMS_WORK_SOURCE_KEY = "WorkSource";
187     /** {@hide} */
188     public static final String REQUEST_PACKAGE_NAME_KEY = "PackageName";
189 
190     /**
191      * scan configuration parameters to be sent to {@link #startBackgroundScan}
192      */
193     public static class ScanSettings implements Parcelable {
194         /**
195          * Hidden network to be scanned for.
196          * {@hide}
197          */
198         public static class HiddenNetwork {
199             /** SSID of the network */
200             public String ssid;
201 
202             /**
203              * Default constructor for HiddenNetwork.
204              */
HiddenNetwork(String ssid)205             public HiddenNetwork(String ssid) {
206                 this.ssid = ssid;
207             }
208         }
209 
210         /** one of the WIFI_BAND values */
211         public int band;
212         /** list of channels; used when band is set to WIFI_BAND_UNSPECIFIED */
213         public ChannelSpec[] channels;
214         /**
215          * list of hidden networks to scan for. Explicit probe requests are sent out for such
216          * networks during scan. Only valid for single scan requests.
217          * {@hide}
218          */
219         @RequiresPermission(android.Manifest.permission.NETWORK_STACK)
220         public HiddenNetwork[] hiddenNetworks;
221         /** period of background scan; in millisecond, 0 => single shot scan */
222         public int periodInMs;
223         /** must have a valid REPORT_EVENT value */
224         public int reportEvents;
225         /** defines number of bssids to cache from each scan */
226         public int numBssidsPerScan;
227         /**
228          * defines number of scans to cache; use it with REPORT_EVENT_AFTER_BUFFER_FULL
229          * to wake up at fixed interval
230          */
231         public int maxScansToCache;
232         /**
233          * if maxPeriodInMs is non zero or different than period, then this bucket is
234          * a truncated binary exponential backoff bucket and the scan period will grow
235          * exponentially as per formula: actual_period(N) = period * (2 ^ (N/stepCount))
236          * to maxPeriodInMs
237          */
238         public int maxPeriodInMs;
239         /**
240          * for truncated binary exponential back off bucket, number of scans to perform
241          * for a given period
242          */
243         public int stepCount;
244         /**
245          * Flag to indicate if the scan settings are targeted for PNO scan.
246          * {@hide}
247          */
248         public boolean isPnoScan;
249         /**
250          * Indicate the type of scan to be performed by the wifi chip.
251          * Default value: {@link #TYPE_LOW_LATENCY}.
252          * {@hide}
253          */
254         @RequiresPermission(android.Manifest.permission.NETWORK_STACK)
255         public int type = TYPE_LOW_LATENCY;
256         /**
257          * This scan request may ignore location settings while receiving scans. This should only
258          * be used in emergency situations.
259          * {@hide}
260          */
261         @SystemApi
262         public boolean ignoreLocationSettings;
263         /**
264          * This scan request will be hidden from app-ops noting for location information. This
265          * should only be used by FLP/NLP module on the device which is using the scan results to
266          * compute results for behalf on their clients. FLP/NLP module using this flag should ensure
267          * that they note in app-ops the eventual delivery of location information computed using
268          * these results to their client .
269          * {@hide}
270          */
271         @SystemApi
272         public boolean hideFromAppOps;
273 
274         /** Implement the Parcelable interface {@hide} */
describeContents()275         public int describeContents() {
276             return 0;
277         }
278 
279         /** Implement the Parcelable interface {@hide} */
writeToParcel(Parcel dest, int flags)280         public void writeToParcel(Parcel dest, int flags) {
281             dest.writeInt(band);
282             dest.writeInt(periodInMs);
283             dest.writeInt(reportEvents);
284             dest.writeInt(numBssidsPerScan);
285             dest.writeInt(maxScansToCache);
286             dest.writeInt(maxPeriodInMs);
287             dest.writeInt(stepCount);
288             dest.writeInt(isPnoScan ? 1 : 0);
289             dest.writeInt(type);
290             dest.writeInt(ignoreLocationSettings ? 1 : 0);
291             dest.writeInt(hideFromAppOps ? 1 : 0);
292             if (channels != null) {
293                 dest.writeInt(channels.length);
294                 for (int i = 0; i < channels.length; i++) {
295                     dest.writeInt(channels[i].frequency);
296                     dest.writeInt(channels[i].dwellTimeMS);
297                     dest.writeInt(channels[i].passive ? 1 : 0);
298                 }
299             } else {
300                 dest.writeInt(0);
301             }
302             if (hiddenNetworks != null) {
303                 dest.writeInt(hiddenNetworks.length);
304                 for (int i = 0; i < hiddenNetworks.length; i++) {
305                     dest.writeString(hiddenNetworks[i].ssid);
306                 }
307             } else {
308                 dest.writeInt(0);
309             }
310         }
311 
312         /** Implement the Parcelable interface {@hide} */
313         public static final @android.annotation.NonNull Creator<ScanSettings> CREATOR =
314                 new Creator<ScanSettings>() {
315                     public ScanSettings createFromParcel(Parcel in) {
316                         ScanSettings settings = new ScanSettings();
317                         settings.band = in.readInt();
318                         settings.periodInMs = in.readInt();
319                         settings.reportEvents = in.readInt();
320                         settings.numBssidsPerScan = in.readInt();
321                         settings.maxScansToCache = in.readInt();
322                         settings.maxPeriodInMs = in.readInt();
323                         settings.stepCount = in.readInt();
324                         settings.isPnoScan = in.readInt() == 1;
325                         settings.type = in.readInt();
326                         settings.ignoreLocationSettings = in.readInt() == 1;
327                         settings.hideFromAppOps = in.readInt() == 1;
328                         int num_channels = in.readInt();
329                         settings.channels = new ChannelSpec[num_channels];
330                         for (int i = 0; i < num_channels; i++) {
331                             int frequency = in.readInt();
332                             ChannelSpec spec = new ChannelSpec(frequency);
333                             spec.dwellTimeMS = in.readInt();
334                             spec.passive = in.readInt() == 1;
335                             settings.channels[i] = spec;
336                         }
337                         int numNetworks = in.readInt();
338                         settings.hiddenNetworks = new HiddenNetwork[numNetworks];
339                         for (int i = 0; i < numNetworks; i++) {
340                             String ssid = in.readString();
341                             settings.hiddenNetworks[i] = new HiddenNetwork(ssid);;
342                         }
343                         return settings;
344                     }
345 
346                     public ScanSettings[] newArray(int size) {
347                         return new ScanSettings[size];
348                     }
349                 };
350 
351     }
352 
353     /**
354      * all the information garnered from a single scan
355      */
356     public static class ScanData implements Parcelable {
357         /** scan identifier */
358         private int mId;
359         /** additional information about scan
360          * 0 => no special issues encountered in the scan
361          * non-zero => scan was truncated, so results may not be complete
362          */
363         private int mFlags;
364         /**
365          * Indicates the buckets that were scanned to generate these results.
366          * This is not relevant to WifiScanner API users and is used internally.
367          * {@hide}
368          */
369         private int mBucketsScanned;
370         /**
371          * Bands scanned. One of the WIFI_BAND values.
372          * Will be {@link #WIFI_BAND_UNSPECIFIED} if the list of channels do not fully cover
373          * any of the bands.
374          * {@hide}
375          */
376         private int mBandScanned;
377         /** all scan results discovered in this scan, sorted by timestamp in ascending order */
378         private ScanResult mResults[];
379 
ScanData()380         ScanData() {}
381 
ScanData(int id, int flags, ScanResult[] results)382         public ScanData(int id, int flags, ScanResult[] results) {
383             mId = id;
384             mFlags = flags;
385             mResults = results;
386         }
387 
388         /** {@hide} */
ScanData(int id, int flags, int bucketsScanned, int bandScanned, ScanResult[] results)389         public ScanData(int id, int flags, int bucketsScanned, int bandScanned,
390                         ScanResult[] results) {
391             mId = id;
392             mFlags = flags;
393             mBucketsScanned = bucketsScanned;
394             mBandScanned = bandScanned;
395             mResults = results;
396         }
397 
ScanData(ScanData s)398         public ScanData(ScanData s) {
399             mId = s.mId;
400             mFlags = s.mFlags;
401             mBucketsScanned = s.mBucketsScanned;
402             mBandScanned = s.mBandScanned;
403             mResults = new ScanResult[s.mResults.length];
404             for (int i = 0; i < s.mResults.length; i++) {
405                 ScanResult result = s.mResults[i];
406                 ScanResult newResult = new ScanResult(result);
407                 mResults[i] = newResult;
408             }
409         }
410 
getId()411         public int getId() {
412             return mId;
413         }
414 
getFlags()415         public int getFlags() {
416             return mFlags;
417         }
418 
419         /** {@hide} */
getBucketsScanned()420         public int getBucketsScanned() {
421             return mBucketsScanned;
422         }
423 
424         /** {@hide} */
getBandScanned()425         public int getBandScanned() {
426             return mBandScanned;
427         }
428 
getResults()429         public ScanResult[] getResults() {
430             return mResults;
431         }
432 
433         /** Implement the Parcelable interface {@hide} */
describeContents()434         public int describeContents() {
435             return 0;
436         }
437 
438         /** Implement the Parcelable interface {@hide} */
writeToParcel(Parcel dest, int flags)439         public void writeToParcel(Parcel dest, int flags) {
440             if (mResults != null) {
441                 dest.writeInt(mId);
442                 dest.writeInt(mFlags);
443                 dest.writeInt(mBucketsScanned);
444                 dest.writeInt(mBandScanned);
445                 dest.writeInt(mResults.length);
446                 for (int i = 0; i < mResults.length; i++) {
447                     ScanResult result = mResults[i];
448                     result.writeToParcel(dest, flags);
449                 }
450             } else {
451                 dest.writeInt(0);
452             }
453         }
454 
455         /** Implement the Parcelable interface {@hide} */
456         public static final @android.annotation.NonNull Creator<ScanData> CREATOR =
457                 new Creator<ScanData>() {
458                     public ScanData createFromParcel(Parcel in) {
459                         int id = in.readInt();
460                         int flags = in.readInt();
461                         int bucketsScanned = in.readInt();
462                         int bandScanned = in.readInt();
463                         int n = in.readInt();
464                         ScanResult results[] = new ScanResult[n];
465                         for (int i = 0; i < n; i++) {
466                             results[i] = ScanResult.CREATOR.createFromParcel(in);
467                         }
468                         return new ScanData(id, flags, bucketsScanned, bandScanned, results);
469                     }
470 
471                     public ScanData[] newArray(int size) {
472                         return new ScanData[size];
473                     }
474                 };
475     }
476 
477     public static class ParcelableScanData implements Parcelable {
478 
479         public ScanData mResults[];
480 
ParcelableScanData(ScanData[] results)481         public ParcelableScanData(ScanData[] results) {
482             mResults = results;
483         }
484 
getResults()485         public ScanData[] getResults() {
486             return mResults;
487         }
488 
489         /** Implement the Parcelable interface {@hide} */
describeContents()490         public int describeContents() {
491             return 0;
492         }
493 
494         /** Implement the Parcelable interface {@hide} */
writeToParcel(Parcel dest, int flags)495         public void writeToParcel(Parcel dest, int flags) {
496             if (mResults != null) {
497                 dest.writeInt(mResults.length);
498                 for (int i = 0; i < mResults.length; i++) {
499                     ScanData result = mResults[i];
500                     result.writeToParcel(dest, flags);
501                 }
502             } else {
503                 dest.writeInt(0);
504             }
505         }
506 
507         /** Implement the Parcelable interface {@hide} */
508         public static final @android.annotation.NonNull Creator<ParcelableScanData> CREATOR =
509                 new Creator<ParcelableScanData>() {
510                     public ParcelableScanData createFromParcel(Parcel in) {
511                         int n = in.readInt();
512                         ScanData results[] = new ScanData[n];
513                         for (int i = 0; i < n; i++) {
514                             results[i] = ScanData.CREATOR.createFromParcel(in);
515                         }
516                         return new ParcelableScanData(results);
517                     }
518 
519                     public ParcelableScanData[] newArray(int size) {
520                         return new ParcelableScanData[size];
521                     }
522                 };
523     }
524 
525     public static class ParcelableScanResults implements Parcelable {
526 
527         public ScanResult mResults[];
528 
ParcelableScanResults(ScanResult[] results)529         public ParcelableScanResults(ScanResult[] results) {
530             mResults = results;
531         }
532 
getResults()533         public ScanResult[] getResults() {
534             return mResults;
535         }
536 
537         /** Implement the Parcelable interface {@hide} */
describeContents()538         public int describeContents() {
539             return 0;
540         }
541 
542         /** Implement the Parcelable interface {@hide} */
writeToParcel(Parcel dest, int flags)543         public void writeToParcel(Parcel dest, int flags) {
544             if (mResults != null) {
545                 dest.writeInt(mResults.length);
546                 for (int i = 0; i < mResults.length; i++) {
547                     ScanResult result = mResults[i];
548                     result.writeToParcel(dest, flags);
549                 }
550             } else {
551                 dest.writeInt(0);
552             }
553         }
554 
555         /** Implement the Parcelable interface {@hide} */
556         public static final @android.annotation.NonNull Creator<ParcelableScanResults> CREATOR =
557                 new Creator<ParcelableScanResults>() {
558                     public ParcelableScanResults createFromParcel(Parcel in) {
559                         int n = in.readInt();
560                         ScanResult results[] = new ScanResult[n];
561                         for (int i = 0; i < n; i++) {
562                             results[i] = ScanResult.CREATOR.createFromParcel(in);
563                         }
564                         return new ParcelableScanResults(results);
565                     }
566 
567                     public ParcelableScanResults[] newArray(int size) {
568                         return new ParcelableScanResults[size];
569                     }
570                 };
571     }
572 
573     /** {@hide} */
574     public static final String PNO_PARAMS_PNO_SETTINGS_KEY = "PnoSettings";
575     /** {@hide} */
576     public static final String PNO_PARAMS_SCAN_SETTINGS_KEY = "ScanSettings";
577     /**
578      * PNO scan configuration parameters to be sent to {@link #startPnoScan}.
579      * Note: This structure needs to be in sync with |wifi_epno_params| struct in gscan HAL API.
580      * {@hide}
581      */
582     public static class PnoSettings implements Parcelable {
583         /**
584          * Pno network to be added to the PNO scan filtering.
585          * {@hide}
586          */
587         public static class PnoNetwork {
588             /*
589              * Pno flags bitmask to be set in {@link #PnoNetwork.flags}
590              */
591             /** Whether directed scan needs to be performed (for hidden SSIDs) */
592             public static final byte FLAG_DIRECTED_SCAN = (1 << 0);
593             /** Whether PNO event shall be triggered if the network is found on A band */
594             public static final byte FLAG_A_BAND = (1 << 1);
595             /** Whether PNO event shall be triggered if the network is found on G band */
596             public static final byte FLAG_G_BAND = (1 << 2);
597             /**
598              * Whether strict matching is required
599              * If required then the firmware must store the network's SSID and not just a hash
600              */
601             public static final byte FLAG_STRICT_MATCH = (1 << 3);
602             /**
603              * If this SSID should be considered the same network as the currently connected
604              * one for scoring.
605              */
606             public static final byte FLAG_SAME_NETWORK = (1 << 4);
607 
608             /*
609              * Code for matching the beacon AUTH IE - additional codes. Bitmask to be set in
610              * {@link #PnoNetwork.authBitField}
611              */
612             /** Open Network */
613             public static final byte AUTH_CODE_OPEN = (1 << 0);
614             /** WPA_PSK or WPA2PSK */
615             public static final byte AUTH_CODE_PSK = (1 << 1);
616             /** any EAPOL */
617             public static final byte AUTH_CODE_EAPOL = (1 << 2);
618 
619             /** SSID of the network */
620             public String ssid;
621             /** Bitmask of the FLAG_XXX */
622             public byte flags = 0;
623             /** Bitmask of the ATUH_XXX */
624             public byte authBitField = 0;
625             /** frequencies on which the particular network needs to be scanned for */
626             public int[] frequencies = {};
627 
628             /**
629              * default constructor for PnoNetwork
630              */
PnoNetwork(String ssid)631             public PnoNetwork(String ssid) {
632                 this.ssid = ssid;
633             }
634         }
635 
636         /** Connected vs Disconnected PNO flag {@hide} */
637         public boolean isConnected;
638         /** Minimum 5GHz RSSI for a BSSID to be considered */
639         public int min5GHzRssi;
640         /** Minimum 2.4GHz RSSI for a BSSID to be considered */
641         public int min24GHzRssi;
642         /** Maximum score that a network can have before bonuses */
643         public int initialScoreMax;
644         /**
645          *  Only report when there is a network's score this much higher
646          *  than the current connection.
647          */
648         public int currentConnectionBonus;
649         /** score bonus for all networks with the same network flag */
650         public int sameNetworkBonus;
651         /** score bonus for networks that are not open */
652         public int secureBonus;
653         /** 5GHz RSSI score bonus (applied to all 5GHz networks) */
654         public int band5GHzBonus;
655         /** Pno Network filter list */
656         public PnoNetwork[] networkList;
657 
658         /** Implement the Parcelable interface {@hide} */
describeContents()659         public int describeContents() {
660             return 0;
661         }
662 
663         /** Implement the Parcelable interface {@hide} */
writeToParcel(Parcel dest, int flags)664         public void writeToParcel(Parcel dest, int flags) {
665             dest.writeInt(isConnected ? 1 : 0);
666             dest.writeInt(min5GHzRssi);
667             dest.writeInt(min24GHzRssi);
668             dest.writeInt(initialScoreMax);
669             dest.writeInt(currentConnectionBonus);
670             dest.writeInt(sameNetworkBonus);
671             dest.writeInt(secureBonus);
672             dest.writeInt(band5GHzBonus);
673             if (networkList != null) {
674                 dest.writeInt(networkList.length);
675                 for (int i = 0; i < networkList.length; i++) {
676                     dest.writeString(networkList[i].ssid);
677                     dest.writeByte(networkList[i].flags);
678                     dest.writeByte(networkList[i].authBitField);
679                     dest.writeIntArray(networkList[i].frequencies);
680                 }
681             } else {
682                 dest.writeInt(0);
683             }
684         }
685 
686         /** Implement the Parcelable interface {@hide} */
687         public static final @android.annotation.NonNull Creator<PnoSettings> CREATOR =
688                 new Creator<PnoSettings>() {
689                     public PnoSettings createFromParcel(Parcel in) {
690                         PnoSettings settings = new PnoSettings();
691                         settings.isConnected = in.readInt() == 1;
692                         settings.min5GHzRssi = in.readInt();
693                         settings.min24GHzRssi = in.readInt();
694                         settings.initialScoreMax = in.readInt();
695                         settings.currentConnectionBonus = in.readInt();
696                         settings.sameNetworkBonus = in.readInt();
697                         settings.secureBonus = in.readInt();
698                         settings.band5GHzBonus = in.readInt();
699                         int numNetworks = in.readInt();
700                         settings.networkList = new PnoNetwork[numNetworks];
701                         for (int i = 0; i < numNetworks; i++) {
702                             String ssid = in.readString();
703                             PnoNetwork network = new PnoNetwork(ssid);
704                             network.flags = in.readByte();
705                             network.authBitField = in.readByte();
706                             network.frequencies = in.createIntArray();
707                             settings.networkList[i] = network;
708                         }
709                         return settings;
710                     }
711 
712                     public PnoSettings[] newArray(int size) {
713                         return new PnoSettings[size];
714                     }
715                 };
716 
717     }
718 
719     /**
720      * interface to get scan events on; specify this on {@link #startBackgroundScan} or
721      * {@link #startScan}
722      */
723     public interface ScanListener extends ActionListener {
724         /**
725          * Framework co-ordinates scans across multiple apps; so it may not give exactly the
726          * same period requested. If period of a scan is changed; it is reported by this event.
727          */
onPeriodChanged(int periodInMs)728         public void onPeriodChanged(int periodInMs);
729         /**
730          * reports results retrieved from background scan and single shot scans
731          */
onResults(ScanData[] results)732         public void onResults(ScanData[] results);
733         /**
734          * reports full scan result for each access point found in scan
735          */
onFullResult(ScanResult fullScanResult)736         public void onFullResult(ScanResult fullScanResult);
737     }
738 
739     /**
740      * interface to get PNO scan events on; specify this on {@link #startDisconnectedPnoScan} and
741      * {@link #startConnectedPnoScan}.
742      * {@hide}
743      */
744     public interface PnoScanListener extends ScanListener {
745         /**
746          * Invoked when one of the PNO networks are found in scan results.
747          */
onPnoNetworkFound(ScanResult[] results)748         void onPnoNetworkFound(ScanResult[] results);
749     }
750 
751     /**
752      * Enable/Disable wifi scanning.
753      *
754      * {@hide}
755      */
756     @RequiresPermission(Manifest.permission.NETWORK_STACK)
setScanningEnabled(boolean enable)757     public void setScanningEnabled(boolean enable) {
758         validateChannel();
759         mAsyncChannel.sendMessage(enable ? CMD_ENABLE : CMD_DISABLE);
760     }
761 
762     /**
763      * Register a listener that will receive results from all single scans
764      * Either the onSuccess/onFailure will be called once when the listener is registered. After
765      * (assuming onSuccess was called) all subsequent single scan results will be delivered to the
766      * listener. It is possible that onFullResult will not be called for all results of the first
767      * scan if the listener was registered during the scan.
768      *
769      * @param listener specifies the object to report events to. This object is also treated as a
770      *                 key for this request, and must also be specified to cancel the request.
771      *                 Multiple requests should also not share this object.
772      * {@hide}
773      */
774     @RequiresPermission(Manifest.permission.NETWORK_STACK)
registerScanListener(ScanListener listener)775     public void registerScanListener(ScanListener listener) {
776         Preconditions.checkNotNull(listener, "listener cannot be null");
777         int key = addListener(listener);
778         if (key == INVALID_KEY) return;
779         validateChannel();
780         mAsyncChannel.sendMessage(CMD_REGISTER_SCAN_LISTENER, 0, key);
781     }
782 
783     /**
784      * Deregister a listener for ongoing single scans
785      * @param listener specifies which scan to cancel; must be same object as passed in {@link
786      *  #registerScanListener}
787      * {@hide}
788      */
deregisterScanListener(ScanListener listener)789     public void deregisterScanListener(ScanListener listener) {
790         Preconditions.checkNotNull(listener, "listener cannot be null");
791         int key = removeListener(listener);
792         if (key == INVALID_KEY) return;
793         validateChannel();
794         mAsyncChannel.sendMessage(CMD_DEREGISTER_SCAN_LISTENER, 0, key);
795     }
796 
797     /** start wifi scan in background
798      * @param settings specifies various parameters for the scan; for more information look at
799      * {@link ScanSettings}
800      * @param listener specifies the object to report events to. This object is also treated as a
801      *                 key for this scan, and must also be specified to cancel the scan. Multiple
802      *                 scans should also not share this object.
803      */
804     @RequiresPermission(android.Manifest.permission.LOCATION_HARDWARE)
startBackgroundScan(ScanSettings settings, ScanListener listener)805     public void startBackgroundScan(ScanSettings settings, ScanListener listener) {
806         startBackgroundScan(settings, listener, null);
807     }
808 
809     /** start wifi scan in background
810      * @param settings specifies various parameters for the scan; for more information look at
811      * {@link ScanSettings}
812      * @param workSource WorkSource to blame for power usage
813      * @param listener specifies the object to report events to. This object is also treated as a
814      *                 key for this scan, and must also be specified to cancel the scan. Multiple
815      *                 scans should also not share this object.
816      */
817     @RequiresPermission(android.Manifest.permission.LOCATION_HARDWARE)
startBackgroundScan(ScanSettings settings, ScanListener listener, WorkSource workSource)818     public void startBackgroundScan(ScanSettings settings, ScanListener listener,
819             WorkSource workSource) {
820         Preconditions.checkNotNull(listener, "listener cannot be null");
821         int key = addListener(listener);
822         if (key == INVALID_KEY) return;
823         validateChannel();
824         Bundle scanParams = new Bundle();
825         scanParams.putParcelable(SCAN_PARAMS_SCAN_SETTINGS_KEY, settings);
826         scanParams.putParcelable(SCAN_PARAMS_WORK_SOURCE_KEY, workSource);
827         scanParams.putString(REQUEST_PACKAGE_NAME_KEY, mContext.getOpPackageName());
828         mAsyncChannel.sendMessage(CMD_START_BACKGROUND_SCAN, 0, key, scanParams);
829     }
830 
831     /**
832      * stop an ongoing wifi scan
833      * @param listener specifies which scan to cancel; must be same object as passed in {@link
834      *  #startBackgroundScan}
835      */
836     @RequiresPermission(android.Manifest.permission.LOCATION_HARDWARE)
stopBackgroundScan(ScanListener listener)837     public void stopBackgroundScan(ScanListener listener) {
838         Preconditions.checkNotNull(listener, "listener cannot be null");
839         int key = removeListener(listener);
840         if (key == INVALID_KEY) return;
841         validateChannel();
842         Bundle scanParams = new Bundle();
843         scanParams.putString(REQUEST_PACKAGE_NAME_KEY, mContext.getOpPackageName());
844         mAsyncChannel.sendMessage(CMD_STOP_BACKGROUND_SCAN, 0, key, scanParams);
845     }
846 
847     /**
848      * reports currently available scan results on appropriate listeners
849      * @return true if all scan results were reported correctly
850      */
851     @RequiresPermission(android.Manifest.permission.LOCATION_HARDWARE)
getScanResults()852     public boolean getScanResults() {
853         validateChannel();
854         Bundle scanParams = new Bundle();
855         scanParams.putString(REQUEST_PACKAGE_NAME_KEY, mContext.getOpPackageName());
856         Message reply =
857                 mAsyncChannel.sendMessageSynchronously(CMD_GET_SCAN_RESULTS, 0, 0, scanParams);
858         return reply.what == CMD_OP_SUCCEEDED;
859     }
860 
861     /**
862      * starts a single scan and reports results asynchronously
863      * @param settings specifies various parameters for the scan; for more information look at
864      * {@link ScanSettings}
865      * @param listener specifies the object to report events to. This object is also treated as a
866      *                 key for this scan, and must also be specified to cancel the scan. Multiple
867      *                 scans should also not share this object.
868      */
869     @RequiresPermission(android.Manifest.permission.LOCATION_HARDWARE)
startScan(ScanSettings settings, ScanListener listener)870     public void startScan(ScanSettings settings, ScanListener listener) {
871         startScan(settings, listener, null);
872     }
873 
874     /**
875      * starts a single scan and reports results asynchronously
876      * @param settings specifies various parameters for the scan; for more information look at
877      * {@link ScanSettings}
878      * @param workSource WorkSource to blame for power usage
879      * @param listener specifies the object to report events to. This object is also treated as a
880      *                 key for this scan, and must also be specified to cancel the scan. Multiple
881      *                 scans should also not share this object.
882      */
883     @RequiresPermission(android.Manifest.permission.LOCATION_HARDWARE)
startScan(ScanSettings settings, ScanListener listener, WorkSource workSource)884     public void startScan(ScanSettings settings, ScanListener listener, WorkSource workSource) {
885         Preconditions.checkNotNull(listener, "listener cannot be null");
886         int key = addListener(listener);
887         if (key == INVALID_KEY) return;
888         validateChannel();
889         Bundle scanParams = new Bundle();
890         scanParams.putParcelable(SCAN_PARAMS_SCAN_SETTINGS_KEY, settings);
891         scanParams.putParcelable(SCAN_PARAMS_WORK_SOURCE_KEY, workSource);
892         scanParams.putString(REQUEST_PACKAGE_NAME_KEY, mContext.getOpPackageName());
893         mAsyncChannel.sendMessage(CMD_START_SINGLE_SCAN, 0, key, scanParams);
894     }
895 
896     /**
897      * stops an ongoing single shot scan; only useful after {@link #startScan} if onResults()
898      * hasn't been called on the listener, ignored otherwise
899      * @param listener
900      */
901     @RequiresPermission(android.Manifest.permission.LOCATION_HARDWARE)
stopScan(ScanListener listener)902     public void stopScan(ScanListener listener) {
903         Preconditions.checkNotNull(listener, "listener cannot be null");
904         int key = removeListener(listener);
905         if (key == INVALID_KEY) return;
906         validateChannel();
907         Bundle scanParams = new Bundle();
908         scanParams.putString(REQUEST_PACKAGE_NAME_KEY, mContext.getOpPackageName());
909         mAsyncChannel.sendMessage(CMD_STOP_SINGLE_SCAN, 0, key, scanParams);
910     }
911 
912     /**
913      * Retrieve the most recent scan results from a single scan request.
914      * {@hide}
915      */
getSingleScanResults()916     public List<ScanResult> getSingleScanResults() {
917         validateChannel();
918         Bundle scanParams = new Bundle();
919         scanParams.putString(REQUEST_PACKAGE_NAME_KEY, mContext.getOpPackageName());
920         Message reply = mAsyncChannel.sendMessageSynchronously(CMD_GET_SINGLE_SCAN_RESULTS, 0, 0,
921                 scanParams);
922         if (reply.what == WifiScanner.CMD_OP_SUCCEEDED) {
923             return Arrays.asList(((ParcelableScanResults) reply.obj).getResults());
924         }
925         OperationResult result = (OperationResult) reply.obj;
926         Log.e(TAG, "Error retrieving SingleScan results reason: " + result.reason
927                 + " description: " + result.description);
928         return new ArrayList<ScanResult>();
929     }
930 
startPnoScan(ScanSettings scanSettings, PnoSettings pnoSettings, int key)931     private void startPnoScan(ScanSettings scanSettings, PnoSettings pnoSettings, int key) {
932         // Bundle up both the settings and send it across.
933         Bundle pnoParams = new Bundle();
934         // Set the PNO scan flag.
935         scanSettings.isPnoScan = true;
936         pnoParams.putParcelable(PNO_PARAMS_SCAN_SETTINGS_KEY, scanSettings);
937         pnoParams.putParcelable(PNO_PARAMS_PNO_SETTINGS_KEY, pnoSettings);
938         mAsyncChannel.sendMessage(CMD_START_PNO_SCAN, 0, key, pnoParams);
939     }
940     /**
941      * Start wifi connected PNO scan
942      * @param scanSettings specifies various parameters for the scan; for more information look at
943      * {@link ScanSettings}
944      * @param pnoSettings specifies various parameters for PNO; for more information look at
945      * {@link PnoSettings}
946      * @param listener specifies the object to report events to. This object is also treated as a
947      *                 key for this scan, and must also be specified to cancel the scan. Multiple
948      *                 scans should also not share this object.
949      * {@hide}
950      */
startConnectedPnoScan(ScanSettings scanSettings, PnoSettings pnoSettings, PnoScanListener listener)951     public void startConnectedPnoScan(ScanSettings scanSettings, PnoSettings pnoSettings,
952             PnoScanListener listener) {
953         Preconditions.checkNotNull(listener, "listener cannot be null");
954         Preconditions.checkNotNull(pnoSettings, "pnoSettings cannot be null");
955         int key = addListener(listener);
956         if (key == INVALID_KEY) return;
957         validateChannel();
958         pnoSettings.isConnected = true;
959         startPnoScan(scanSettings, pnoSettings, key);
960     }
961     /**
962      * Start wifi disconnected PNO scan
963      * @param scanSettings specifies various parameters for the scan; for more information look at
964      * {@link ScanSettings}
965      * @param pnoSettings specifies various parameters for PNO; for more information look at
966      * {@link PnoSettings}
967      * @param listener specifies the object to report events to. This object is also treated as a
968      *                 key for this scan, and must also be specified to cancel the scan. Multiple
969      *                 scans should also not share this object.
970      * {@hide}
971      */
972     @RequiresPermission(android.Manifest.permission.NETWORK_STACK)
startDisconnectedPnoScan(ScanSettings scanSettings, PnoSettings pnoSettings, PnoScanListener listener)973     public void startDisconnectedPnoScan(ScanSettings scanSettings, PnoSettings pnoSettings,
974             PnoScanListener listener) {
975         Preconditions.checkNotNull(listener, "listener cannot be null");
976         Preconditions.checkNotNull(pnoSettings, "pnoSettings cannot be null");
977         int key = addListener(listener);
978         if (key == INVALID_KEY) return;
979         validateChannel();
980         pnoSettings.isConnected = false;
981         startPnoScan(scanSettings, pnoSettings, key);
982     }
983     /**
984      * Stop an ongoing wifi PNO scan
985      * @param listener specifies which scan to cancel; must be same object as passed in {@link
986      *  #startPnoScan}
987      * {@hide}
988      */
989     @RequiresPermission(android.Manifest.permission.NETWORK_STACK)
stopPnoScan(ScanListener listener)990     public void stopPnoScan(ScanListener listener) {
991         Preconditions.checkNotNull(listener, "listener cannot be null");
992         int key = removeListener(listener);
993         if (key == INVALID_KEY) return;
994         validateChannel();
995         mAsyncChannel.sendMessage(CMD_STOP_PNO_SCAN, 0, key);
996     }
997 
998     /** specifies information about an access point of interest */
999     @Deprecated
1000     public static class BssidInfo {
1001         /** bssid of the access point; in XX:XX:XX:XX:XX:XX format */
1002         public String bssid;
1003         /** low signal strength threshold; more information at {@link ScanResult#level} */
1004         public int low;                                            /* minimum RSSI */
1005         /** high signal threshold; more information at {@link ScanResult#level} */
1006         public int high;                                           /* maximum RSSI */
1007         /** channel frequency (in KHz) where you may find this BSSID */
1008         public int frequencyHint;
1009     }
1010 
1011     /** @hide */
1012     @SystemApi
1013     @Deprecated
1014     public static class WifiChangeSettings implements Parcelable {
1015         public int rssiSampleSize;                          /* sample size for RSSI averaging */
1016         public int lostApSampleSize;                        /* samples to confirm AP's loss */
1017         public int unchangedSampleSize;                     /* samples to confirm no change */
1018         public int minApsBreachingThreshold;                /* change threshold to trigger event */
1019         public int periodInMs;                              /* scan period in millisecond */
1020         public BssidInfo[] bssidInfos;
1021 
1022         /** Implement the Parcelable interface {@hide} */
describeContents()1023         public int describeContents() {
1024             return 0;
1025         }
1026 
1027         /** Implement the Parcelable interface {@hide} */
writeToParcel(Parcel dest, int flags)1028         public void writeToParcel(Parcel dest, int flags) {
1029         }
1030 
1031         /** Implement the Parcelable interface {@hide} */
1032         public static final @android.annotation.NonNull Creator<WifiChangeSettings> CREATOR =
1033                 new Creator<WifiChangeSettings>() {
1034                     public WifiChangeSettings createFromParcel(Parcel in) {
1035                         return new WifiChangeSettings();
1036                     }
1037 
1038                     public WifiChangeSettings[] newArray(int size) {
1039                         return new WifiChangeSettings[size];
1040                     }
1041                 };
1042 
1043     }
1044 
1045     /** configure WifiChange detection
1046      * @param rssiSampleSize number of samples used for RSSI averaging
1047      * @param lostApSampleSize number of samples to confirm an access point's loss
1048      * @param unchangedSampleSize number of samples to confirm there are no changes
1049      * @param minApsBreachingThreshold minimum number of access points that need to be
1050      *                                 out of range to detect WifiChange
1051      * @param periodInMs indicates period of scan to find changes
1052      * @param bssidInfos access points to watch
1053      */
1054     @Deprecated
1055     @SuppressLint("Doclava125")
configureWifiChange( int rssiSampleSize, int lostApSampleSize, int unchangedSampleSize, int minApsBreachingThreshold, int periodInMs, BssidInfo[] bssidInfos )1056     public void configureWifiChange(
1057             int rssiSampleSize,                             /* sample size for RSSI averaging */
1058             int lostApSampleSize,                           /* samples to confirm AP's loss */
1059             int unchangedSampleSize,                        /* samples to confirm no change */
1060             int minApsBreachingThreshold,                   /* change threshold to trigger event */
1061             int periodInMs,                                 /* period of scan */
1062             BssidInfo[] bssidInfos                          /* signal thresholds to cross */
1063             )
1064     {
1065         throw new UnsupportedOperationException();
1066     }
1067 
1068     /**
1069      * interface to get wifi change events on; use this on {@link #startTrackingWifiChange}
1070      */
1071     @Deprecated
1072     public interface WifiChangeListener extends ActionListener {
1073         /** indicates that changes were detected in wifi environment
1074          * @param results indicate the access points that exhibited change
1075          */
onChanging(ScanResult[] results)1076         public void onChanging(ScanResult[] results);           /* changes are found */
1077         /** indicates that no wifi changes are being detected for a while
1078          * @param results indicate the access points that are bing monitored for change
1079          */
onQuiescence(ScanResult[] results)1080         public void onQuiescence(ScanResult[] results);         /* changes settled down */
1081     }
1082 
1083     /**
1084      * track changes in wifi environment
1085      * @param listener object to report events on; this object must be unique and must also be
1086      *                 provided on {@link #stopTrackingWifiChange}
1087      */
1088     @Deprecated
1089     @SuppressLint("Doclava125")
startTrackingWifiChange(WifiChangeListener listener)1090     public void startTrackingWifiChange(WifiChangeListener listener) {
1091         throw new UnsupportedOperationException();
1092     }
1093 
1094     /**
1095      * stop tracking changes in wifi environment
1096      * @param listener object that was provided to report events on {@link
1097      * #stopTrackingWifiChange}
1098      */
1099     @Deprecated
1100     @SuppressLint("Doclava125")
stopTrackingWifiChange(WifiChangeListener listener)1101     public void stopTrackingWifiChange(WifiChangeListener listener) {
1102         throw new UnsupportedOperationException();
1103     }
1104 
1105     /** @hide */
1106     @SystemApi
1107     @Deprecated
1108     @SuppressLint("Doclava125")
configureWifiChange(WifiChangeSettings settings)1109     public void configureWifiChange(WifiChangeSettings settings) {
1110         throw new UnsupportedOperationException();
1111     }
1112 
1113     /** interface to receive hotlist events on; use this on {@link #setHotlist} */
1114     @Deprecated
1115     public static interface BssidListener extends ActionListener {
1116         /** indicates that access points were found by on going scans
1117          * @param results list of scan results, one for each access point visible currently
1118          */
onFound(ScanResult[] results)1119         public void onFound(ScanResult[] results);
1120         /** indicates that access points were missed by on going scans
1121          * @param results list of scan results, for each access point that is not visible anymore
1122          */
onLost(ScanResult[] results)1123         public void onLost(ScanResult[] results);
1124     }
1125 
1126     /** @hide */
1127     @SystemApi
1128     @Deprecated
1129     public static class HotlistSettings implements Parcelable {
1130         public BssidInfo[] bssidInfos;
1131         public int apLostThreshold;
1132 
1133         /** Implement the Parcelable interface {@hide} */
describeContents()1134         public int describeContents() {
1135             return 0;
1136         }
1137 
1138         /** Implement the Parcelable interface {@hide} */
writeToParcel(Parcel dest, int flags)1139         public void writeToParcel(Parcel dest, int flags) {
1140         }
1141 
1142         /** Implement the Parcelable interface {@hide} */
1143         public static final @android.annotation.NonNull Creator<HotlistSettings> CREATOR =
1144                 new Creator<HotlistSettings>() {
1145                     public HotlistSettings createFromParcel(Parcel in) {
1146                         HotlistSettings settings = new HotlistSettings();
1147                         return settings;
1148                     }
1149 
1150                     public HotlistSettings[] newArray(int size) {
1151                         return new HotlistSettings[size];
1152                     }
1153                 };
1154     }
1155 
1156     /**
1157      * set interesting access points to find
1158      * @param bssidInfos access points of interest
1159      * @param apLostThreshold number of scans needed to indicate that AP is lost
1160      * @param listener object provided to report events on; this object must be unique and must
1161      *                 also be provided on {@link #stopTrackingBssids}
1162      */
1163     @Deprecated
1164     @SuppressLint("Doclava125")
startTrackingBssids(BssidInfo[] bssidInfos, int apLostThreshold, BssidListener listener)1165     public void startTrackingBssids(BssidInfo[] bssidInfos,
1166                                     int apLostThreshold, BssidListener listener) {
1167         throw new UnsupportedOperationException();
1168     }
1169 
1170     /**
1171      * remove tracking of interesting access points
1172      * @param listener same object provided in {@link #startTrackingBssids}
1173      */
1174     @Deprecated
1175     @SuppressLint("Doclava125")
stopTrackingBssids(BssidListener listener)1176     public void stopTrackingBssids(BssidListener listener) {
1177         throw new UnsupportedOperationException();
1178     }
1179 
1180 
1181     /* private members and methods */
1182 
1183     private static final String TAG = "WifiScanner";
1184     private static final boolean DBG = false;
1185 
1186     /* commands for Wifi Service */
1187     private static final int BASE = Protocol.BASE_WIFI_SCANNER;
1188 
1189     /** @hide */
1190     public static final int CMD_START_BACKGROUND_SCAN       = BASE + 2;
1191     /** @hide */
1192     public static final int CMD_STOP_BACKGROUND_SCAN        = BASE + 3;
1193     /** @hide */
1194     public static final int CMD_GET_SCAN_RESULTS            = BASE + 4;
1195     /** @hide */
1196     public static final int CMD_SCAN_RESULT                 = BASE + 5;
1197     /** @hide */
1198     public static final int CMD_OP_SUCCEEDED                = BASE + 17;
1199     /** @hide */
1200     public static final int CMD_OP_FAILED                   = BASE + 18;
1201     /** @hide */
1202     public static final int CMD_FULL_SCAN_RESULT            = BASE + 20;
1203     /** @hide */
1204     public static final int CMD_START_SINGLE_SCAN           = BASE + 21;
1205     /** @hide */
1206     public static final int CMD_STOP_SINGLE_SCAN            = BASE + 22;
1207     /** @hide */
1208     public static final int CMD_SINGLE_SCAN_COMPLETED       = BASE + 23;
1209     /** @hide */
1210     public static final int CMD_START_PNO_SCAN              = BASE + 24;
1211     /** @hide */
1212     public static final int CMD_STOP_PNO_SCAN               = BASE + 25;
1213     /** @hide */
1214     public static final int CMD_PNO_NETWORK_FOUND           = BASE + 26;
1215     /** @hide */
1216     public static final int CMD_REGISTER_SCAN_LISTENER      = BASE + 27;
1217     /** @hide */
1218     public static final int CMD_DEREGISTER_SCAN_LISTENER    = BASE + 28;
1219     /** @hide */
1220     public static final int CMD_GET_SINGLE_SCAN_RESULTS     = BASE + 29;
1221     /** @hide */
1222     public static final int CMD_ENABLE                      = BASE + 30;
1223     /** @hide */
1224     public static final int CMD_DISABLE                     = BASE + 31;
1225 
1226     private Context mContext;
1227     private IWifiScanner mService;
1228 
1229     private static final int INVALID_KEY = 0;
1230     private int mListenerKey = 1;
1231 
1232     private final SparseArray mListenerMap = new SparseArray();
1233     private final Object mListenerMapLock = new Object();
1234 
1235     private AsyncChannel mAsyncChannel;
1236     private final Handler mInternalHandler;
1237 
1238     /**
1239      * Create a new WifiScanner instance.
1240      * Applications will almost always want to use
1241      * {@link android.content.Context#getSystemService Context.getSystemService()} to retrieve
1242      * the standard {@link android.content.Context#WIFI_SERVICE Context.WIFI_SERVICE}.
1243      * @param context the application context
1244      * @param service the Binder interface
1245      * @param looper the Looper used to deliver callbacks
1246      * @hide
1247      */
WifiScanner(Context context, IWifiScanner service, Looper looper)1248     public WifiScanner(Context context, IWifiScanner service, Looper looper) {
1249         mContext = context;
1250         mService = service;
1251 
1252         Messenger messenger = null;
1253         try {
1254             messenger = mService.getMessenger();
1255         } catch (RemoteException e) {
1256             throw e.rethrowFromSystemServer();
1257         }
1258 
1259         if (messenger == null) {
1260             throw new IllegalStateException("getMessenger() returned null!  This is invalid.");
1261         }
1262 
1263         mAsyncChannel = new AsyncChannel();
1264 
1265         mInternalHandler = new ServiceHandler(looper);
1266         mAsyncChannel.connectSync(mContext, mInternalHandler, messenger);
1267         // We cannot use fullyConnectSync because it sends the FULL_CONNECTION message
1268         // synchronously, which causes WifiScanningService to receive the wrong replyTo value.
1269         mAsyncChannel.sendMessage(AsyncChannel.CMD_CHANNEL_FULL_CONNECTION);
1270     }
1271 
validateChannel()1272     private void validateChannel() {
1273         if (mAsyncChannel == null) throw new IllegalStateException(
1274                 "No permission to access and change wifi or a bad initialization");
1275     }
1276 
1277     // Add a listener into listener map. If the listener already exists, return INVALID_KEY and
1278     // send an error message to internal handler; Otherwise add the listener to the listener map and
1279     // return the key of the listener.
addListener(ActionListener listener)1280     private int addListener(ActionListener listener) {
1281         synchronized (mListenerMapLock) {
1282             boolean keyExists = (getListenerKey(listener) != INVALID_KEY);
1283             // Note we need to put the listener into listener map even if it's a duplicate as the
1284             // internal handler will need the key to find the listener. In case of duplicates,
1285             // removing duplicate key logic will be handled in internal handler.
1286             int key = putListener(listener);
1287             if (keyExists) {
1288                 if (DBG) Log.d(TAG, "listener key already exists");
1289                 OperationResult operationResult = new OperationResult(REASON_DUPLICATE_REQEUST,
1290                         "Outstanding request with same key not stopped yet");
1291                 Message message = Message.obtain(mInternalHandler, CMD_OP_FAILED, 0, key,
1292                         operationResult);
1293                 message.sendToTarget();
1294                 return INVALID_KEY;
1295             } else {
1296                 return key;
1297             }
1298         }
1299     }
1300 
putListener(Object listener)1301     private int putListener(Object listener) {
1302         if (listener == null) return INVALID_KEY;
1303         int key;
1304         synchronized (mListenerMapLock) {
1305             do {
1306                 key = mListenerKey++;
1307             } while (key == INVALID_KEY);
1308             mListenerMap.put(key, listener);
1309         }
1310         return key;
1311     }
1312 
getListener(int key)1313     private Object getListener(int key) {
1314         if (key == INVALID_KEY) return null;
1315         synchronized (mListenerMapLock) {
1316             Object listener = mListenerMap.get(key);
1317             return listener;
1318         }
1319     }
1320 
getListenerKey(Object listener)1321     private int getListenerKey(Object listener) {
1322         if (listener == null) return INVALID_KEY;
1323         synchronized (mListenerMapLock) {
1324             int index = mListenerMap.indexOfValue(listener);
1325             if (index == -1) {
1326                 return INVALID_KEY;
1327             } else {
1328                 return mListenerMap.keyAt(index);
1329             }
1330         }
1331     }
1332 
removeListener(int key)1333     private Object removeListener(int key) {
1334         if (key == INVALID_KEY) return null;
1335         synchronized (mListenerMapLock) {
1336             Object listener = mListenerMap.get(key);
1337             mListenerMap.remove(key);
1338             return listener;
1339         }
1340     }
1341 
removeListener(Object listener)1342     private int removeListener(Object listener) {
1343         int key = getListenerKey(listener);
1344         if (key == INVALID_KEY) {
1345             Log.e(TAG, "listener cannot be found");
1346             return key;
1347         }
1348         synchronized (mListenerMapLock) {
1349             mListenerMap.remove(key);
1350             return key;
1351         }
1352     }
1353 
1354     /** @hide */
1355     public static class OperationResult implements Parcelable {
1356         public int reason;
1357         public String description;
1358 
OperationResult(int reason, String description)1359         public OperationResult(int reason, String description) {
1360             this.reason = reason;
1361             this.description = description;
1362         }
1363 
1364         /** Implement the Parcelable interface {@hide} */
describeContents()1365         public int describeContents() {
1366             return 0;
1367         }
1368 
1369         /** Implement the Parcelable interface {@hide} */
writeToParcel(Parcel dest, int flags)1370         public void writeToParcel(Parcel dest, int flags) {
1371             dest.writeInt(reason);
1372             dest.writeString(description);
1373         }
1374 
1375         /** Implement the Parcelable interface {@hide} */
1376         public static final @android.annotation.NonNull Creator<OperationResult> CREATOR =
1377                 new Creator<OperationResult>() {
1378                     public OperationResult createFromParcel(Parcel in) {
1379                         int reason = in.readInt();
1380                         String description = in.readString();
1381                         return new OperationResult(reason, description);
1382                     }
1383 
1384                     public OperationResult[] newArray(int size) {
1385                         return new OperationResult[size];
1386                     }
1387                 };
1388     }
1389 
1390     private class ServiceHandler extends Handler {
ServiceHandler(Looper looper)1391         ServiceHandler(Looper looper) {
1392             super(looper);
1393         }
1394         @Override
handleMessage(Message msg)1395         public void handleMessage(Message msg) {
1396             switch (msg.what) {
1397                 case AsyncChannel.CMD_CHANNEL_FULLY_CONNECTED:
1398                     return;
1399                 case AsyncChannel.CMD_CHANNEL_DISCONNECTED:
1400                     Log.e(TAG, "Channel connection lost");
1401                     // This will cause all further async API calls on the WifiManager
1402                     // to fail and throw an exception
1403                     mAsyncChannel = null;
1404                     getLooper().quit();
1405                     return;
1406             }
1407 
1408             Object listener = getListener(msg.arg2);
1409 
1410             if (listener == null) {
1411                 if (DBG) Log.d(TAG, "invalid listener key = " + msg.arg2);
1412                 return;
1413             } else {
1414                 if (DBG) Log.d(TAG, "listener key = " + msg.arg2);
1415             }
1416 
1417             switch (msg.what) {
1418                     /* ActionListeners grouped together */
1419                 case CMD_OP_SUCCEEDED :
1420                     ((ActionListener) listener).onSuccess();
1421                     break;
1422                 case CMD_OP_FAILED : {
1423                         OperationResult result = (OperationResult)msg.obj;
1424                         ((ActionListener) listener).onFailure(result.reason, result.description);
1425                         removeListener(msg.arg2);
1426                     }
1427                     break;
1428                 case CMD_SCAN_RESULT :
1429                     ((ScanListener) listener).onResults(
1430                             ((ParcelableScanData) msg.obj).getResults());
1431                     return;
1432                 case CMD_FULL_SCAN_RESULT :
1433                     ScanResult result = (ScanResult) msg.obj;
1434                     ((ScanListener) listener).onFullResult(result);
1435                     return;
1436                 case CMD_SINGLE_SCAN_COMPLETED:
1437                     if (DBG) Log.d(TAG, "removing listener for single scan");
1438                     removeListener(msg.arg2);
1439                     break;
1440                 case CMD_PNO_NETWORK_FOUND:
1441                     ((PnoScanListener) listener).onPnoNetworkFound(
1442                             ((ParcelableScanResults) msg.obj).getResults());
1443                     return;
1444                 default:
1445                     if (DBG) Log.d(TAG, "Ignoring message " + msg.what);
1446                     return;
1447             }
1448         }
1449     }
1450 }
1451