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