• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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     /**
660      * Register a listener that will receive results from all single scans
661      * Either the onSuccess/onFailure will be called once when the listener is registered. After
662      * (assuming onSuccess was called) all subsequent single scan results will be delivered to the
663      * listener. It is possible that onFullResult will not be called for all results of the first
664      * scan if the listener was registered during the scan.
665      *
666      * @param listener specifies the object to report events to. This object is also treated as a
667      *                 key for this request, and must also be specified to cancel the request.
668      *                 Multiple requests should also not share this object.
669      * {@hide}
670      */
registerScanListener(ScanListener listener)671     public void registerScanListener(ScanListener listener) {
672         Preconditions.checkNotNull(listener, "listener cannot be null");
673         int key = addListener(listener);
674         if (key == INVALID_KEY) return;
675         validateChannel();
676         mAsyncChannel.sendMessage(CMD_REGISTER_SCAN_LISTENER, 0, key);
677     }
678 
679     /**
680      * Deregister a listener for ongoing single scans
681      * @param listener specifies which scan to cancel; must be same object as passed in {@link
682      *  #registerScanListener}
683      * {@hide}
684      */
deregisterScanListener(ScanListener listener)685     public void deregisterScanListener(ScanListener listener) {
686         Preconditions.checkNotNull(listener, "listener cannot be null");
687         int key = removeListener(listener);
688         if (key == INVALID_KEY) return;
689         validateChannel();
690         mAsyncChannel.sendMessage(CMD_DEREGISTER_SCAN_LISTENER, 0, key);
691     }
692 
693     /** start wifi scan in background
694      * @param settings specifies various parameters for the scan; for more information look at
695      * {@link ScanSettings}
696      * @param listener specifies the object to report events to. This object is also treated as a
697      *                 key for this scan, and must also be specified to cancel the scan. Multiple
698      *                 scans should also not share this object.
699      */
startBackgroundScan(ScanSettings settings, ScanListener listener)700     public void startBackgroundScan(ScanSettings settings, ScanListener listener) {
701         startBackgroundScan(settings, listener, null);
702     }
703 
704     /** start wifi scan in background
705      * @param settings specifies various parameters for the scan; for more information look at
706      * {@link ScanSettings}
707      * @param workSource WorkSource to blame for power usage
708      * @param listener specifies the object to report events to. This object is also treated as a
709      *                 key for this scan, and must also be specified to cancel the scan. Multiple
710      *                 scans should also not share this object.
711      */
startBackgroundScan(ScanSettings settings, ScanListener listener, WorkSource workSource)712     public void startBackgroundScan(ScanSettings settings, ScanListener listener,
713             WorkSource workSource) {
714         Preconditions.checkNotNull(listener, "listener cannot be null");
715         int key = addListener(listener);
716         if (key == INVALID_KEY) return;
717         validateChannel();
718         Bundle scanParams = new Bundle();
719         scanParams.putParcelable(SCAN_PARAMS_SCAN_SETTINGS_KEY, settings);
720         scanParams.putParcelable(SCAN_PARAMS_WORK_SOURCE_KEY, workSource);
721         mAsyncChannel.sendMessage(CMD_START_BACKGROUND_SCAN, 0, key, scanParams);
722     }
723 
724     /**
725      * stop an ongoing wifi scan
726      * @param listener specifies which scan to cancel; must be same object as passed in {@link
727      *  #startBackgroundScan}
728      */
stopBackgroundScan(ScanListener listener)729     public void stopBackgroundScan(ScanListener listener) {
730         Preconditions.checkNotNull(listener, "listener cannot be null");
731         int key = removeListener(listener);
732         if (key == INVALID_KEY) return;
733         validateChannel();
734         mAsyncChannel.sendMessage(CMD_STOP_BACKGROUND_SCAN, 0, key);
735     }
736     /**
737      * reports currently available scan results on appropriate listeners
738      * @return true if all scan results were reported correctly
739      */
getScanResults()740     public boolean getScanResults() {
741         validateChannel();
742         Message reply = mAsyncChannel.sendMessageSynchronously(CMD_GET_SCAN_RESULTS, 0);
743         return reply.what == CMD_OP_SUCCEEDED;
744     }
745 
746     /**
747      * starts a single scan and reports results asynchronously
748      * @param settings specifies various parameters for the scan; for more information look at
749      * {@link ScanSettings}
750      * @param listener specifies the object to report events to. This object is also treated as a
751      *                 key for this scan, and must also be specified to cancel the scan. Multiple
752      *                 scans should also not share this object.
753      */
startScan(ScanSettings settings, ScanListener listener)754     public void startScan(ScanSettings settings, ScanListener listener) {
755         startScan(settings, listener, null);
756     }
757 
758     /**
759      * starts a single scan and reports results asynchronously
760      * @param settings specifies various parameters for the scan; for more information look at
761      * {@link ScanSettings}
762      * @param workSource WorkSource to blame for power usage
763      * @param listener specifies the object to report events to. This object is also treated as a
764      *                 key for this scan, and must also be specified to cancel the scan. Multiple
765      *                 scans should also not share this object.
766      */
startScan(ScanSettings settings, ScanListener listener, WorkSource workSource)767     public void startScan(ScanSettings settings, ScanListener listener, WorkSource workSource) {
768         Preconditions.checkNotNull(listener, "listener cannot be null");
769         int key = addListener(listener);
770         if (key == INVALID_KEY) return;
771         validateChannel();
772         Bundle scanParams = new Bundle();
773         scanParams.putParcelable(SCAN_PARAMS_SCAN_SETTINGS_KEY, settings);
774         scanParams.putParcelable(SCAN_PARAMS_WORK_SOURCE_KEY, workSource);
775         mAsyncChannel.sendMessage(CMD_START_SINGLE_SCAN, 0, key, scanParams);
776     }
777 
778     /**
779      * stops an ongoing single shot scan; only useful after {@link #startScan} if onResults()
780      * hasn't been called on the listener, ignored otherwise
781      * @param listener
782      */
stopScan(ScanListener listener)783     public void stopScan(ScanListener listener) {
784         Preconditions.checkNotNull(listener, "listener cannot be null");
785         int key = removeListener(listener);
786         if (key == INVALID_KEY) return;
787         validateChannel();
788         mAsyncChannel.sendMessage(CMD_STOP_SINGLE_SCAN, 0, key);
789     }
790 
startPnoScan(ScanSettings scanSettings, PnoSettings pnoSettings, int key)791     private void startPnoScan(ScanSettings scanSettings, PnoSettings pnoSettings, int key) {
792         // Bundle up both the settings and send it across.
793         Bundle pnoParams = new Bundle();
794         // Set the PNO scan flag.
795         scanSettings.isPnoScan = true;
796         pnoParams.putParcelable(PNO_PARAMS_SCAN_SETTINGS_KEY, scanSettings);
797         pnoParams.putParcelable(PNO_PARAMS_PNO_SETTINGS_KEY, pnoSettings);
798         mAsyncChannel.sendMessage(CMD_START_PNO_SCAN, 0, key, pnoParams);
799     }
800     /**
801      * Start wifi connected PNO scan
802      * @param scanSettings specifies various parameters for the scan; for more information look at
803      * {@link ScanSettings}
804      * @param pnoSettings specifies various parameters for PNO; for more information look at
805      * {@link PnoSettings}
806      * @param listener specifies the object to report events to. This object is also treated as a
807      *                 key for this scan, and must also be specified to cancel the scan. Multiple
808      *                 scans should also not share this object.
809      * {@hide}
810      */
startConnectedPnoScan(ScanSettings scanSettings, PnoSettings pnoSettings, PnoScanListener listener)811     public void startConnectedPnoScan(ScanSettings scanSettings, PnoSettings pnoSettings,
812             PnoScanListener listener) {
813         Preconditions.checkNotNull(listener, "listener cannot be null");
814         Preconditions.checkNotNull(pnoSettings, "pnoSettings cannot be null");
815         int key = addListener(listener);
816         if (key == INVALID_KEY) return;
817         validateChannel();
818         pnoSettings.isConnected = true;
819         startPnoScan(scanSettings, pnoSettings, key);
820     }
821     /**
822      * Start wifi disconnected PNO scan
823      * @param scanSettings specifies various parameters for the scan; for more information look at
824      * {@link ScanSettings}
825      * @param pnoSettings specifies various parameters for PNO; for more information look at
826      * {@link PnoSettings}
827      * @param listener specifies the object to report events to. This object is also treated as a
828      *                 key for this scan, and must also be specified to cancel the scan. Multiple
829      *                 scans should also not share this object.
830      * {@hide}
831      */
startDisconnectedPnoScan(ScanSettings scanSettings, PnoSettings pnoSettings, PnoScanListener listener)832     public void startDisconnectedPnoScan(ScanSettings scanSettings, PnoSettings pnoSettings,
833             PnoScanListener listener) {
834         Preconditions.checkNotNull(listener, "listener cannot be null");
835         Preconditions.checkNotNull(pnoSettings, "pnoSettings cannot be null");
836         int key = addListener(listener);
837         if (key == INVALID_KEY) return;
838         validateChannel();
839         pnoSettings.isConnected = false;
840         startPnoScan(scanSettings, pnoSettings, key);
841     }
842     /**
843      * Stop an ongoing wifi PNO scan
844      * @param listener specifies which scan to cancel; must be same object as passed in {@link
845      *  #startPnoScan}
846      * TODO(rpius): Check if we can remove pnoSettings param in stop.
847      * {@hide}
848      */
stopPnoScan(ScanListener listener)849     public void stopPnoScan(ScanListener listener) {
850         Preconditions.checkNotNull(listener, "listener cannot be null");
851         int key = removeListener(listener);
852         if (key == INVALID_KEY) return;
853         validateChannel();
854         mAsyncChannel.sendMessage(CMD_STOP_PNO_SCAN, 0, key);
855     }
856 
857     /** specifies information about an access point of interest */
858     public static class BssidInfo {
859         /** bssid of the access point; in XX:XX:XX:XX:XX:XX format */
860         public String bssid;
861         /** low signal strength threshold; more information at {@link ScanResult#level} */
862         public int low;                                            /* minimum RSSI */
863         /** high signal threshold; more information at {@link ScanResult#level} */
864         public int high;                                           /* maximum RSSI */
865         /** channel frequency (in KHz) where you may find this BSSID */
866         public int frequencyHint;
867     }
868 
869     /** @hide */
870     @SystemApi
871     public static class WifiChangeSettings implements Parcelable {
872         public int rssiSampleSize;                          /* sample size for RSSI averaging */
873         public int lostApSampleSize;                        /* samples to confirm AP's loss */
874         public int unchangedSampleSize;                     /* samples to confirm no change */
875         public int minApsBreachingThreshold;                /* change threshold to trigger event */
876         public int periodInMs;                              /* scan period in millisecond */
877         public BssidInfo[] bssidInfos;
878 
879         /** Implement the Parcelable interface {@hide} */
describeContents()880         public int describeContents() {
881             return 0;
882         }
883 
884         /** Implement the Parcelable interface {@hide} */
writeToParcel(Parcel dest, int flags)885         public void writeToParcel(Parcel dest, int flags) {
886             dest.writeInt(rssiSampleSize);
887             dest.writeInt(lostApSampleSize);
888             dest.writeInt(unchangedSampleSize);
889             dest.writeInt(minApsBreachingThreshold);
890             dest.writeInt(periodInMs);
891             if (bssidInfos != null) {
892                 dest.writeInt(bssidInfos.length);
893                 for (int i = 0; i < bssidInfos.length; i++) {
894                     BssidInfo info = bssidInfos[i];
895                     dest.writeString(info.bssid);
896                     dest.writeInt(info.low);
897                     dest.writeInt(info.high);
898                     dest.writeInt(info.frequencyHint);
899                 }
900             } else {
901                 dest.writeInt(0);
902             }
903         }
904 
905         /** Implement the Parcelable interface {@hide} */
906         public static final Creator<WifiChangeSettings> CREATOR =
907                 new Creator<WifiChangeSettings>() {
908                     public WifiChangeSettings createFromParcel(Parcel in) {
909                         WifiChangeSettings settings = new WifiChangeSettings();
910                         settings.rssiSampleSize = in.readInt();
911                         settings.lostApSampleSize = in.readInt();
912                         settings.unchangedSampleSize = in.readInt();
913                         settings.minApsBreachingThreshold = in.readInt();
914                         settings.periodInMs = in.readInt();
915                         int len = in.readInt();
916                         settings.bssidInfos = new BssidInfo[len];
917                         for (int i = 0; i < len; i++) {
918                             BssidInfo info = new BssidInfo();
919                             info.bssid = in.readString();
920                             info.low = in.readInt();
921                             info.high = in.readInt();
922                             info.frequencyHint = in.readInt();
923                             settings.bssidInfos[i] = info;
924                         }
925                         return settings;
926                     }
927 
928                     public WifiChangeSettings[] newArray(int size) {
929                         return new WifiChangeSettings[size];
930                     }
931                 };
932 
933     }
934 
935     /** configure WifiChange detection
936      * @param rssiSampleSize number of samples used for RSSI averaging
937      * @param lostApSampleSize number of samples to confirm an access point's loss
938      * @param unchangedSampleSize number of samples to confirm there are no changes
939      * @param minApsBreachingThreshold minimum number of access points that need to be
940      *                                 out of range to detect WifiChange
941      * @param periodInMs indicates period of scan to find changes
942      * @param bssidInfos access points to watch
943      */
configureWifiChange( int rssiSampleSize, int lostApSampleSize, int unchangedSampleSize, int minApsBreachingThreshold, int periodInMs, BssidInfo[] bssidInfos )944     public void configureWifiChange(
945             int rssiSampleSize,                             /* sample size for RSSI averaging */
946             int lostApSampleSize,                           /* samples to confirm AP's loss */
947             int unchangedSampleSize,                        /* samples to confirm no change */
948             int minApsBreachingThreshold,                   /* change threshold to trigger event */
949             int periodInMs,                                 /* period of scan */
950             BssidInfo[] bssidInfos                          /* signal thresholds to crosss */
951             )
952     {
953         validateChannel();
954 
955         WifiChangeSettings settings = new WifiChangeSettings();
956         settings.rssiSampleSize = rssiSampleSize;
957         settings.lostApSampleSize = lostApSampleSize;
958         settings.unchangedSampleSize = unchangedSampleSize;
959         settings.minApsBreachingThreshold = minApsBreachingThreshold;
960         settings.periodInMs = periodInMs;
961         settings.bssidInfos = bssidInfos;
962 
963         configureWifiChange(settings);
964     }
965 
966     /**
967      * interface to get wifi change events on; use this on {@link #startTrackingWifiChange}
968      */
969     public interface WifiChangeListener extends ActionListener {
970         /** indicates that changes were detected in wifi environment
971          * @param results indicate the access points that exhibited change
972          */
onChanging(ScanResult[] results)973         public void onChanging(ScanResult[] results);           /* changes are found */
974         /** indicates that no wifi changes are being detected for a while
975          * @param results indicate the access points that are bing monitored for change
976          */
onQuiescence(ScanResult[] results)977         public void onQuiescence(ScanResult[] results);         /* changes settled down */
978     }
979 
980     /**
981      * track changes in wifi environment
982      * @param listener object to report events on; this object must be unique and must also be
983      *                 provided on {@link #stopTrackingWifiChange}
984      */
startTrackingWifiChange(WifiChangeListener listener)985     public void startTrackingWifiChange(WifiChangeListener listener) {
986         Preconditions.checkNotNull(listener, "listener cannot be null");
987         int key = addListener(listener);
988         if (key == INVALID_KEY) return;
989         validateChannel();
990         mAsyncChannel.sendMessage(CMD_START_TRACKING_CHANGE, 0, key);
991     }
992 
993     /**
994      * stop tracking changes in wifi environment
995      * @param listener object that was provided to report events on {@link
996      * #stopTrackingWifiChange}
997      */
stopTrackingWifiChange(WifiChangeListener listener)998     public void stopTrackingWifiChange(WifiChangeListener listener) {
999         int key = removeListener(listener);
1000         if (key == INVALID_KEY) return;
1001         validateChannel();
1002         mAsyncChannel.sendMessage(CMD_STOP_TRACKING_CHANGE, 0, key);
1003     }
1004 
1005     /** @hide */
1006     @SystemApi
configureWifiChange(WifiChangeSettings settings)1007     public void configureWifiChange(WifiChangeSettings settings) {
1008         validateChannel();
1009         mAsyncChannel.sendMessage(CMD_CONFIGURE_WIFI_CHANGE, 0, 0, settings);
1010     }
1011 
1012     /** interface to receive hotlist events on; use this on {@link #setHotlist} */
1013     public static interface BssidListener extends ActionListener {
1014         /** indicates that access points were found by on going scans
1015          * @param results list of scan results, one for each access point visible currently
1016          */
onFound(ScanResult[] results)1017         public void onFound(ScanResult[] results);
1018         /** indicates that access points were missed by on going scans
1019          * @param results list of scan results, for each access point that is not visible anymore
1020          */
onLost(ScanResult[] results)1021         public void onLost(ScanResult[] results);
1022     }
1023 
1024     /** @hide */
1025     @SystemApi
1026     public static class HotlistSettings implements Parcelable {
1027         public BssidInfo[] bssidInfos;
1028         public int apLostThreshold;
1029 
1030         /** Implement the Parcelable interface {@hide} */
describeContents()1031         public int describeContents() {
1032             return 0;
1033         }
1034 
1035         /** Implement the Parcelable interface {@hide} */
writeToParcel(Parcel dest, int flags)1036         public void writeToParcel(Parcel dest, int flags) {
1037             dest.writeInt(apLostThreshold);
1038 
1039             if (bssidInfos != null) {
1040                 dest.writeInt(bssidInfos.length);
1041                 for (int i = 0; i < bssidInfos.length; i++) {
1042                     BssidInfo info = bssidInfos[i];
1043                     dest.writeString(info.bssid);
1044                     dest.writeInt(info.low);
1045                     dest.writeInt(info.high);
1046                     dest.writeInt(info.frequencyHint);
1047                 }
1048             } else {
1049                 dest.writeInt(0);
1050             }
1051         }
1052 
1053         /** Implement the Parcelable interface {@hide} */
1054         public static final Creator<HotlistSettings> CREATOR =
1055                 new Creator<HotlistSettings>() {
1056                     public HotlistSettings createFromParcel(Parcel in) {
1057                         HotlistSettings settings = new HotlistSettings();
1058                         settings.apLostThreshold = in.readInt();
1059                         int n = in.readInt();
1060                         settings.bssidInfos = new BssidInfo[n];
1061                         for (int i = 0; i < n; i++) {
1062                             BssidInfo info = new BssidInfo();
1063                             info.bssid = in.readString();
1064                             info.low = in.readInt();
1065                             info.high = in.readInt();
1066                             info.frequencyHint = in.readInt();
1067                             settings.bssidInfos[i] = info;
1068                         }
1069                         return settings;
1070                     }
1071 
1072                     public HotlistSettings[] newArray(int size) {
1073                         return new HotlistSettings[size];
1074                     }
1075                 };
1076     }
1077 
1078     /**
1079      * set interesting access points to find
1080      * @param bssidInfos access points of interest
1081      * @param apLostThreshold number of scans needed to indicate that AP is lost
1082      * @param listener object provided to report events on; this object must be unique and must
1083      *                 also be provided on {@link #stopTrackingBssids}
1084      */
startTrackingBssids(BssidInfo[] bssidInfos, int apLostThreshold, BssidListener listener)1085     public void startTrackingBssids(BssidInfo[] bssidInfos,
1086                                     int apLostThreshold, BssidListener listener) {
1087         Preconditions.checkNotNull(listener, "listener cannot be null");
1088         int key = addListener(listener);
1089         if (key == INVALID_KEY) return;
1090         validateChannel();
1091         HotlistSettings settings = new HotlistSettings();
1092         settings.bssidInfos = bssidInfos;
1093         settings.apLostThreshold = apLostThreshold;
1094         mAsyncChannel.sendMessage(CMD_SET_HOTLIST, 0, key, settings);
1095     }
1096 
1097     /**
1098      * remove tracking of interesting access points
1099      * @param listener same object provided in {@link #startTrackingBssids}
1100      */
stopTrackingBssids(BssidListener listener)1101     public void stopTrackingBssids(BssidListener listener) {
1102         Preconditions.checkNotNull(listener, "listener cannot be null");
1103         int key = removeListener(listener);
1104         if (key == INVALID_KEY) return;
1105         validateChannel();
1106         mAsyncChannel.sendMessage(CMD_RESET_HOTLIST, 0, key);
1107     }
1108 
1109 
1110     /* private members and methods */
1111 
1112     private static final String TAG = "WifiScanner";
1113     private static final boolean DBG = false;
1114 
1115     /* commands for Wifi Service */
1116     private static final int BASE = Protocol.BASE_WIFI_SCANNER;
1117 
1118     /** @hide */
1119     public static final int CMD_SCAN                        = BASE + 0;
1120     /** @hide */
1121     public static final int CMD_START_BACKGROUND_SCAN       = BASE + 2;
1122     /** @hide */
1123     public static final int CMD_STOP_BACKGROUND_SCAN        = BASE + 3;
1124     /** @hide */
1125     public static final int CMD_GET_SCAN_RESULTS            = BASE + 4;
1126     /** @hide */
1127     public static final int CMD_SCAN_RESULT                 = BASE + 5;
1128     /** @hide */
1129     public static final int CMD_SET_HOTLIST                 = BASE + 6;
1130     /** @hide */
1131     public static final int CMD_RESET_HOTLIST               = BASE + 7;
1132     /** @hide */
1133     public static final int CMD_AP_FOUND                    = BASE + 9;
1134     /** @hide */
1135     public static final int CMD_AP_LOST                     = BASE + 10;
1136     /** @hide */
1137     public static final int CMD_START_TRACKING_CHANGE       = BASE + 11;
1138     /** @hide */
1139     public static final int CMD_STOP_TRACKING_CHANGE        = BASE + 12;
1140     /** @hide */
1141     public static final int CMD_CONFIGURE_WIFI_CHANGE       = BASE + 13;
1142     /** @hide */
1143     public static final int CMD_WIFI_CHANGE_DETECTED        = BASE + 15;
1144     /** @hide */
1145     public static final int CMD_WIFI_CHANGES_STABILIZED     = BASE + 16;
1146     /** @hide */
1147     public static final int CMD_OP_SUCCEEDED                = BASE + 17;
1148     /** @hide */
1149     public static final int CMD_OP_FAILED                   = BASE + 18;
1150     /** @hide */
1151     public static final int CMD_PERIOD_CHANGED              = BASE + 19;
1152     /** @hide */
1153     public static final int CMD_FULL_SCAN_RESULT            = BASE + 20;
1154     /** @hide */
1155     public static final int CMD_START_SINGLE_SCAN           = BASE + 21;
1156     /** @hide */
1157     public static final int CMD_STOP_SINGLE_SCAN            = BASE + 22;
1158     /** @hide */
1159     public static final int CMD_SINGLE_SCAN_COMPLETED       = BASE + 23;
1160     /** @hide */
1161     public static final int CMD_START_PNO_SCAN              = BASE + 24;
1162     /** @hide */
1163     public static final int CMD_STOP_PNO_SCAN               = BASE + 25;
1164     /** @hide */
1165     public static final int CMD_PNO_NETWORK_FOUND           = BASE + 26;
1166     /** @hide */
1167     public static final int CMD_REGISTER_SCAN_LISTENER      = BASE + 27;
1168     /** @hide */
1169     public static final int CMD_DEREGISTER_SCAN_LISTENER    = BASE + 28;
1170 
1171     private Context mContext;
1172     private IWifiScanner mService;
1173 
1174     private static final int INVALID_KEY = 0;
1175     private int mListenerKey = 1;
1176 
1177     private final SparseArray mListenerMap = new SparseArray();
1178     private final Object mListenerMapLock = new Object();
1179 
1180     private AsyncChannel mAsyncChannel;
1181     private final Handler mInternalHandler;
1182 
1183     /**
1184      * Create a new WifiScanner instance.
1185      * Applications will almost always want to use
1186      * {@link android.content.Context#getSystemService Context.getSystemService()} to retrieve
1187      * the standard {@link android.content.Context#WIFI_SERVICE Context.WIFI_SERVICE}.
1188      * @param context the application context
1189      * @param service the Binder interface
1190      * @param looper the Looper used to deliver callbacks
1191      * @hide
1192      */
WifiScanner(Context context, IWifiScanner service, Looper looper)1193     public WifiScanner(Context context, IWifiScanner service, Looper looper) {
1194         mContext = context;
1195         mService = service;
1196 
1197         Messenger messenger = null;
1198         try {
1199             messenger = mService.getMessenger();
1200         } catch (RemoteException e) {
1201             throw e.rethrowFromSystemServer();
1202         }
1203 
1204         if (messenger == null) {
1205             throw new IllegalStateException("getMessenger() returned null!  This is invalid.");
1206         }
1207 
1208         mAsyncChannel = new AsyncChannel();
1209 
1210         mInternalHandler = new ServiceHandler(looper);
1211         mAsyncChannel.connectSync(mContext, mInternalHandler, messenger);
1212         // We cannot use fullyConnectSync because it sends the FULL_CONNECTION message
1213         // synchronously, which causes WifiScanningService to receive the wrong replyTo value.
1214         mAsyncChannel.sendMessage(AsyncChannel.CMD_CHANNEL_FULL_CONNECTION);
1215     }
1216 
validateChannel()1217     private void validateChannel() {
1218         if (mAsyncChannel == null) throw new IllegalStateException(
1219                 "No permission to access and change wifi or a bad initialization");
1220     }
1221 
1222     // Add a listener into listener map. If the listener already exists, return INVALID_KEY and
1223     // send an error message to internal handler; Otherwise add the listener to the listener map and
1224     // return the key of the listener.
addListener(ActionListener listener)1225     private int addListener(ActionListener listener) {
1226         synchronized (mListenerMapLock) {
1227             boolean keyExists = (getListenerKey(listener) != INVALID_KEY);
1228             // Note we need to put the listener into listener map even if it's a duplicate as the
1229             // internal handler will need the key to find the listener. In case of duplicates,
1230             // removing duplicate key logic will be handled in internal handler.
1231             int key = putListener(listener);
1232             if (keyExists) {
1233                 if (DBG) Log.d(TAG, "listener key already exists");
1234                 OperationResult operationResult = new OperationResult(REASON_DUPLICATE_REQEUST,
1235                         "Outstanding request with same key not stopped yet");
1236                 Message message = Message.obtain(mInternalHandler, CMD_OP_FAILED, 0, key,
1237                         operationResult);
1238                 message.sendToTarget();
1239                 return INVALID_KEY;
1240             } else {
1241                 return key;
1242             }
1243         }
1244     }
1245 
putListener(Object listener)1246     private int putListener(Object listener) {
1247         if (listener == null) return INVALID_KEY;
1248         int key;
1249         synchronized (mListenerMapLock) {
1250             do {
1251                 key = mListenerKey++;
1252             } while (key == INVALID_KEY);
1253             mListenerMap.put(key, listener);
1254         }
1255         return key;
1256     }
1257 
getListener(int key)1258     private Object getListener(int key) {
1259         if (key == INVALID_KEY) return null;
1260         synchronized (mListenerMapLock) {
1261             Object listener = mListenerMap.get(key);
1262             return listener;
1263         }
1264     }
1265 
getListenerKey(Object listener)1266     private int getListenerKey(Object listener) {
1267         if (listener == null) return INVALID_KEY;
1268         synchronized (mListenerMapLock) {
1269             int index = mListenerMap.indexOfValue(listener);
1270             if (index == -1) {
1271                 return INVALID_KEY;
1272             } else {
1273                 return mListenerMap.keyAt(index);
1274             }
1275         }
1276     }
1277 
removeListener(int key)1278     private Object removeListener(int key) {
1279         if (key == INVALID_KEY) return null;
1280         synchronized (mListenerMapLock) {
1281             Object listener = mListenerMap.get(key);
1282             mListenerMap.remove(key);
1283             return listener;
1284         }
1285     }
1286 
removeListener(Object listener)1287     private int removeListener(Object listener) {
1288         int key = getListenerKey(listener);
1289         if (key == INVALID_KEY) {
1290             Log.e(TAG, "listener cannot be found");
1291             return key;
1292         }
1293         synchronized (mListenerMapLock) {
1294             mListenerMap.remove(key);
1295             return key;
1296         }
1297     }
1298 
1299     /** @hide */
1300     public static class OperationResult implements Parcelable {
1301         public int reason;
1302         public String description;
1303 
OperationResult(int reason, String description)1304         public OperationResult(int reason, String description) {
1305             this.reason = reason;
1306             this.description = description;
1307         }
1308 
1309         /** Implement the Parcelable interface {@hide} */
describeContents()1310         public int describeContents() {
1311             return 0;
1312         }
1313 
1314         /** Implement the Parcelable interface {@hide} */
writeToParcel(Parcel dest, int flags)1315         public void writeToParcel(Parcel dest, int flags) {
1316             dest.writeInt(reason);
1317             dest.writeString(description);
1318         }
1319 
1320         /** Implement the Parcelable interface {@hide} */
1321         public static final Creator<OperationResult> CREATOR =
1322                 new Creator<OperationResult>() {
1323                     public OperationResult createFromParcel(Parcel in) {
1324                         int reason = in.readInt();
1325                         String description = in.readString();
1326                         return new OperationResult(reason, description);
1327                     }
1328 
1329                     public OperationResult[] newArray(int size) {
1330                         return new OperationResult[size];
1331                     }
1332                 };
1333     }
1334 
1335     private class ServiceHandler extends Handler {
ServiceHandler(Looper looper)1336         ServiceHandler(Looper looper) {
1337             super(looper);
1338         }
1339         @Override
handleMessage(Message msg)1340         public void handleMessage(Message msg) {
1341             switch (msg.what) {
1342                 case AsyncChannel.CMD_CHANNEL_FULLY_CONNECTED:
1343                     return;
1344                 case AsyncChannel.CMD_CHANNEL_DISCONNECTED:
1345                     Log.e(TAG, "Channel connection lost");
1346                     // This will cause all further async API calls on the WifiManager
1347                     // to fail and throw an exception
1348                     mAsyncChannel = null;
1349                     getLooper().quit();
1350                     return;
1351             }
1352 
1353             Object listener = getListener(msg.arg2);
1354 
1355             if (listener == null) {
1356                 if (DBG) Log.d(TAG, "invalid listener key = " + msg.arg2);
1357                 return;
1358             } else {
1359                 if (DBG) Log.d(TAG, "listener key = " + msg.arg2);
1360             }
1361 
1362             switch (msg.what) {
1363                     /* ActionListeners grouped together */
1364                 case CMD_OP_SUCCEEDED :
1365                     ((ActionListener) listener).onSuccess();
1366                     break;
1367                 case CMD_OP_FAILED : {
1368                         OperationResult result = (OperationResult)msg.obj;
1369                         ((ActionListener) listener).onFailure(result.reason, result.description);
1370                         removeListener(msg.arg2);
1371                     }
1372                     break;
1373                 case CMD_SCAN_RESULT :
1374                     ((ScanListener) listener).onResults(
1375                             ((ParcelableScanData) msg.obj).getResults());
1376                     return;
1377                 case CMD_FULL_SCAN_RESULT :
1378                     ScanResult result = (ScanResult) msg.obj;
1379                     ((ScanListener) listener).onFullResult(result);
1380                     return;
1381                 case CMD_PERIOD_CHANGED:
1382                     ((ScanListener) listener).onPeriodChanged(msg.arg1);
1383                     return;
1384                 case CMD_AP_FOUND:
1385                     ((BssidListener) listener).onFound(
1386                             ((ParcelableScanResults) msg.obj).getResults());
1387                     return;
1388                 case CMD_AP_LOST:
1389                     ((BssidListener) listener).onLost(
1390                             ((ParcelableScanResults) msg.obj).getResults());
1391                     return;
1392                 case CMD_WIFI_CHANGE_DETECTED:
1393                     ((WifiChangeListener) listener).onChanging(
1394                             ((ParcelableScanResults) msg.obj).getResults());
1395                    return;
1396                 case CMD_WIFI_CHANGES_STABILIZED:
1397                     ((WifiChangeListener) listener).onQuiescence(
1398                             ((ParcelableScanResults) msg.obj).getResults());
1399                     return;
1400                 case CMD_SINGLE_SCAN_COMPLETED:
1401                     if (DBG) Log.d(TAG, "removing listener for single scan");
1402                     removeListener(msg.arg2);
1403                     break;
1404                 case CMD_PNO_NETWORK_FOUND:
1405                     ((PnoScanListener) listener).onPnoNetworkFound(
1406                             ((ParcelableScanResults) msg.obj).getResults());
1407                     return;
1408                 default:
1409                     if (DBG) Log.d(TAG, "Ignoring message " + msg.what);
1410                     return;
1411             }
1412         }
1413     }
1414 }
1415