WifiScanner.java revision f45acfe0960f1182ed9d38d2acd78188de25b720
1/*
2 * Copyright (C) 2008 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package android.net.wifi;
18
19import android.annotation.SystemApi;
20import android.content.Context;
21import android.os.Bundle;
22import android.os.Handler;
23import android.os.HandlerThread;
24import android.os.Looper;
25import android.os.Message;
26import android.os.Messenger;
27import android.os.Parcel;
28import android.os.Parcelable;
29import android.os.RemoteException;
30import android.util.Log;
31import android.util.SparseArray;
32
33import com.android.internal.util.AsyncChannel;
34import com.android.internal.util.Protocol;
35
36import java.util.List;
37import java.util.concurrent.CountDownLatch;
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
48public 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
82    /** @hide */
83    public static final String GET_AVAILABLE_CHANNELS_EXTRA = "Channels";
84
85    /**
86     * Generic action callback invocation interface
87     *  @hide
88     */
89    @SystemApi
90    public static interface ActionListener {
91        public void onSuccess();
92        public void onFailure(int reason, String description);
93    }
94
95    /**
96     * gives you all the possible channels; channel is specified as an
97     * integer with frequency in MHz i.e. channel 1 is 2412
98     * @hide
99     */
100    public List<Integer> getAvailableChannels(int band) {
101        try {
102            Bundle bundle =  mService.getAvailableChannels(band);
103            return bundle.getIntegerArrayList(GET_AVAILABLE_CHANNELS_EXTRA);
104        } catch (RemoteException e) {
105            return null;
106        }
107    }
108
109    /**
110     * provides channel specification for scanning
111     */
112    public static class ChannelSpec {
113        /**
114         * channel frequency in MHz; for example channel 1 is specified as 2412
115         */
116        public int frequency;
117        /**
118         * if true, scan this channel in passive fashion.
119         * This flag is ignored on DFS channel specification.
120         * @hide
121         */
122        public boolean passive;                                    /* ignored on DFS channels */
123        /**
124         * how long to dwell on this channel
125         * @hide
126         */
127        public int dwellTimeMS;                                    /* not supported for now */
128
129        /**
130         * default constructor for channel spec
131         */
132        public ChannelSpec(int frequency) {
133            this.frequency = frequency;
134            passive = false;
135            dwellTimeMS = 0;
136        }
137    }
138
139    /** reports {@link ScanListener#onResults} when underlying buffers are full */
140    public static final int REPORT_EVENT_AFTER_BUFFER_FULL = 0;
141    /** reports {@link ScanListener#onResults} after each scan */
142    public static final int REPORT_EVENT_AFTER_EACH_SCAN = 1;
143    /** reports {@link ScanListener#onFullResult} whenever each beacon is discovered */
144    public static final int REPORT_EVENT_FULL_SCAN_RESULT = 2;
145
146    /**
147     * scan configuration parameters to be sent to {@link #startBackgroundScan}
148     */
149    public static class ScanSettings implements Parcelable {
150
151        /** one of the WIFI_BAND values */
152        public int band;
153        /** list of channels; used when band is set to WIFI_BAND_UNSPECIFIED */
154        public ChannelSpec[] channels;
155        /** period of background scan; in millisecond, 0 => single shot scan */
156        public int periodInMs;
157        /** must have a valid REPORT_EVENT value */
158        public int reportEvents;
159        /** defines number of bssids to cache from each scan */
160        public int numBssidsPerScan;
161        /**
162         * defines number of scans to cache; use it with REPORT_EVENT_AFTER_BUFFER_FULL
163         * to wake up at fixed interval
164         */
165        public int maxScansToCache;
166
167        /** Implement the Parcelable interface {@hide} */
168        public int describeContents() {
169            return 0;
170        }
171
172        /** Implement the Parcelable interface {@hide} */
173        public void writeToParcel(Parcel dest, int flags) {
174            dest.writeInt(band);
175            dest.writeInt(periodInMs);
176            dest.writeInt(reportEvents);
177            dest.writeInt(numBssidsPerScan);
178            dest.writeInt(maxScansToCache);
179
180            if (channels != null) {
181                dest.writeInt(channels.length);
182
183                for (int i = 0; i < channels.length; i++) {
184                    dest.writeInt(channels[i].frequency);
185                    dest.writeInt(channels[i].dwellTimeMS);
186                    dest.writeInt(channels[i].passive ? 1 : 0);
187                }
188            } else {
189                dest.writeInt(0);
190            }
191        }
192
193        /** Implement the Parcelable interface {@hide} */
194        public static final Creator<ScanSettings> CREATOR =
195                new Creator<ScanSettings>() {
196                    public ScanSettings createFromParcel(Parcel in) {
197
198                        ScanSettings settings = new ScanSettings();
199                        settings.band = in.readInt();
200                        settings.periodInMs = in.readInt();
201                        settings.reportEvents = in.readInt();
202                        settings.numBssidsPerScan = in.readInt();
203                        settings.maxScansToCache = in.readInt();
204                        int num_channels = in.readInt();
205                        settings.channels = new ChannelSpec[num_channels];
206                        for (int i = 0; i < num_channels; i++) {
207                            int frequency = in.readInt();
208
209                            ChannelSpec spec = new ChannelSpec(frequency);
210                            spec.dwellTimeMS = in.readInt();
211                            spec.passive = in.readInt() == 1;
212                            settings.channels[i] = spec;
213                        }
214
215                        return settings;
216                    }
217
218                    public ScanSettings[] newArray(int size) {
219                        return new ScanSettings[size];
220                    }
221                };
222
223    }
224
225    /**
226     * all the information garnered from a single scan
227     */
228    public static class ScanData implements Parcelable {
229        /** scan identifier */
230        private int mId;
231        /** additional information about scan
232         * 0 => no special issues encountered in the scan
233         * non-zero => scan was truncated, so results may not be complete
234         */
235        private int mFlags;
236        /** all scan results discovered in this scan, sorted by timestamp in ascending order */
237        private ScanResult mResults[];
238
239        ScanData() {}
240
241        public ScanData(int id, int flags, ScanResult[] results) {
242            mId = id;
243            mFlags = flags;
244            mResults = results;
245        }
246
247        public ScanData(ScanData s) {
248            mId = s.mId;
249            mFlags = s.mFlags;
250            mResults = new ScanResult[s.mResults.length];
251            for (int i = 0; i < s.mResults.length; i++) {
252                ScanResult result = s.mResults[i];
253                WifiSsid wifiSsid = WifiSsid.createFromAsciiEncoded(result.SSID);
254                ScanResult newResult = new ScanResult(wifiSsid, result.BSSID, "",
255                        result.level, result.frequency, result.timestamp);
256                mResults[i] = newResult;
257            }
258        }
259
260        public int getId() {
261            return mId;
262        }
263
264        public int getFlags() {
265            return mFlags;
266        }
267
268        public ScanResult[] getResults() {
269            return mResults;
270        }
271
272        /** Implement the Parcelable interface {@hide} */
273        public int describeContents() {
274            return 0;
275        }
276
277        /** Implement the Parcelable interface {@hide} */
278        public void writeToParcel(Parcel dest, int flags) {
279            if (mResults != null) {
280                dest.writeInt(mId);
281                dest.writeInt(mFlags);
282                dest.writeInt(mResults.length);
283                for (int i = 0; i < mResults.length; i++) {
284                    ScanResult result = mResults[i];
285                    result.writeToParcel(dest, flags);
286                }
287            } else {
288                dest.writeInt(0);
289            }
290        }
291
292        /** Implement the Parcelable interface {@hide} */
293        public static final Creator<ScanData> CREATOR =
294                new Creator<ScanData>() {
295                    public ScanData createFromParcel(Parcel in) {
296                        int id = in.readInt();
297                        int flags = in.readInt();
298                        int n = in.readInt();
299                        ScanResult results[] = new ScanResult[n];
300                        for (int i = 0; i < n; i++) {
301                            results[i] = ScanResult.CREATOR.createFromParcel(in);
302                        }
303                        return new ScanData(id, flags, results);
304                    }
305
306                    public ScanData[] newArray(int size) {
307                        return new ScanData[size];
308                    }
309                };
310    }
311
312    public static class ParcelableScanData implements Parcelable {
313
314        public ScanData mResults[];
315
316        public ParcelableScanData(ScanData[] results) {
317            mResults = results;
318        }
319
320        public ScanData[] getResults() {
321            return mResults;
322        }
323
324        /** Implement the Parcelable interface {@hide} */
325        public int describeContents() {
326            return 0;
327        }
328
329        /** Implement the Parcelable interface {@hide} */
330        public void writeToParcel(Parcel dest, int flags) {
331            if (mResults != null) {
332                dest.writeInt(mResults.length);
333                for (int i = 0; i < mResults.length; i++) {
334                    ScanData result = mResults[i];
335                    result.writeToParcel(dest, flags);
336                }
337            } else {
338                dest.writeInt(0);
339            }
340        }
341
342        /** Implement the Parcelable interface {@hide} */
343        public static final Creator<ParcelableScanData> CREATOR =
344                new Creator<ParcelableScanData>() {
345                    public ParcelableScanData createFromParcel(Parcel in) {
346                        int n = in.readInt();
347                        ScanData results[] = new ScanData[n];
348                        for (int i = 0; i < n; i++) {
349                            results[i] = ScanData.CREATOR.createFromParcel(in);
350                        }
351                        return new ParcelableScanData(results);
352                    }
353
354                    public ParcelableScanData[] newArray(int size) {
355                        return new ParcelableScanData[size];
356                    }
357                };
358    }
359
360    public static class ParcelableScanResults implements Parcelable {
361
362        public ScanResult mResults[];
363
364        public ParcelableScanResults(ScanResult[] results) {
365            mResults = results;
366        }
367
368        public ScanResult[] getResults() {
369            return mResults;
370        }
371
372        /** Implement the Parcelable interface {@hide} */
373        public int describeContents() {
374            return 0;
375        }
376
377        /** Implement the Parcelable interface {@hide} */
378        public void writeToParcel(Parcel dest, int flags) {
379            if (mResults != null) {
380                dest.writeInt(mResults.length);
381                for (int i = 0; i < mResults.length; i++) {
382                    ScanResult result = mResults[i];
383                    result.writeToParcel(dest, flags);
384                }
385            } else {
386                dest.writeInt(0);
387            }
388        }
389
390        /** Implement the Parcelable interface {@hide} */
391        public static final Creator<ParcelableScanResults> CREATOR =
392                new Creator<ParcelableScanResults>() {
393                    public ParcelableScanResults createFromParcel(Parcel in) {
394                        int n = in.readInt();
395                        ScanResult results[] = new ScanResult[n];
396                        for (int i = 0; i < n; i++) {
397                            results[i] = ScanResult.CREATOR.createFromParcel(in);
398                        }
399                        return new ParcelableScanResults(results);
400                    }
401
402                    public ParcelableScanResults[] newArray(int size) {
403                        return new ParcelableScanResults[size];
404                    }
405                };
406    }
407
408    /**
409     * interface to get scan events on; specify this on {@link #startBackgroundScan} or
410     * {@link #startScan}
411     */
412    public interface ScanListener extends ActionListener {
413        /**
414         * Framework co-ordinates scans across multiple apps; so it may not give exactly the
415         * same period requested. If period of a scan is changed; it is reported by this event.
416         */
417        public void onPeriodChanged(int periodInMs);
418        /**
419         * reports results retrieved from background scan and single shot scans
420         */
421        public void onResults(ScanData[] results);
422        /**
423         * reports full scan result for each access point found in scan
424         */
425        public void onFullResult(ScanResult fullScanResult);
426    }
427
428    /** start wifi scan in background
429     * @param settings specifies various parameters for the scan; for more information look at
430     * {@link ScanSettings}
431     * @param listener specifies the object to report events to. This object is also treated as a
432     *                 key for this scan, and must also be specified to cancel the scan. Multiple
433     *                 scans should also not share this object.
434     */
435    public void startBackgroundScan(ScanSettings settings, ScanListener listener) {
436        validateChannel();
437        sAsyncChannel.sendMessage(CMD_START_BACKGROUND_SCAN, 0, putListener(listener), settings);
438    }
439    /**
440     * stop an ongoing wifi scan
441     * @param listener specifies which scan to cancel; must be same object as passed in {@link
442     *  #startBackgroundScan}
443     */
444    public void stopBackgroundScan(ScanListener listener) {
445        validateChannel();
446        sAsyncChannel.sendMessage(CMD_STOP_BACKGROUND_SCAN, 0, removeListener(listener));
447    }
448    /**
449     * reports currently available scan results on appropriate listeners
450     * @return true if all scan results were reported correctly
451     */
452    public boolean getScanResults() {
453        validateChannel();
454        Message reply = sAsyncChannel.sendMessageSynchronously(CMD_GET_SCAN_RESULTS, 0);
455        return reply.what == CMD_OP_SUCCEEDED;
456    }
457
458    /**
459     * starts a single scan and reports results asynchronously
460     * @param settings specifies various parameters for the scan; for more information look at
461     * {@link ScanSettings}
462     * @param listener specifies the object to report events to. This object is also treated as a
463     *                 key for this scan, and must also be specified to cancel the scan. Multiple
464     *                 scans should also not share this object.
465     */
466    public void startScan(ScanSettings settings, ScanListener listener) {
467        validateChannel();
468        sAsyncChannel.sendMessage(CMD_START_SINGLE_SCAN, 0, putListener(listener), settings);
469    }
470
471    /**
472     * stops an ongoing single shot scan; only useful after {@link #startScan} if onResults()
473     * hasn't been called on the listener, ignored otherwise
474     * @param listener
475     */
476    public void stopScan(ScanListener listener) {
477        validateChannel();
478        sAsyncChannel.sendMessage(CMD_STOP_SINGLE_SCAN, 0, removeListener(listener));
479    }
480
481    /** specifies information about an access point of interest */
482    public static class BssidInfo {
483        /** bssid of the access point; in XX:XX:XX:XX:XX:XX format */
484        public String bssid;
485        /** low signal strength threshold; more information at {@link ScanResult#level} */
486        public int low;                                            /* minimum RSSI */
487        /** high signal threshold; more information at {@link ScanResult#level} */
488        public int high;                                           /* maximum RSSI */
489        /** channel frequency (in KHz) where you may find this BSSID */
490        public int frequencyHint;
491    }
492
493    /** @hide */
494    @SystemApi
495    public static class WifiChangeSettings implements Parcelable {
496        public int rssiSampleSize;                          /* sample size for RSSI averaging */
497        public int lostApSampleSize;                        /* samples to confirm AP's loss */
498        public int unchangedSampleSize;                     /* samples to confirm no change */
499        public int minApsBreachingThreshold;                /* change threshold to trigger event */
500        public int periodInMs;                              /* scan period in millisecond */
501        public BssidInfo[] bssidInfos;
502
503        /** Implement the Parcelable interface {@hide} */
504        public int describeContents() {
505            return 0;
506        }
507
508        /** Implement the Parcelable interface {@hide} */
509        public void writeToParcel(Parcel dest, int flags) {
510            dest.writeInt(rssiSampleSize);
511            dest.writeInt(lostApSampleSize);
512            dest.writeInt(unchangedSampleSize);
513            dest.writeInt(minApsBreachingThreshold);
514            dest.writeInt(periodInMs);
515            if (bssidInfos != null) {
516                dest.writeInt(bssidInfos.length);
517                for (int i = 0; i < bssidInfos.length; i++) {
518                    BssidInfo info = bssidInfos[i];
519                    dest.writeString(info.bssid);
520                    dest.writeInt(info.low);
521                    dest.writeInt(info.high);
522                    dest.writeInt(info.frequencyHint);
523                }
524            } else {
525                dest.writeInt(0);
526            }
527        }
528
529        /** Implement the Parcelable interface {@hide} */
530        public static final Creator<WifiChangeSettings> CREATOR =
531                new Creator<WifiChangeSettings>() {
532                    public WifiChangeSettings createFromParcel(Parcel in) {
533                        WifiChangeSettings settings = new WifiChangeSettings();
534                        settings.rssiSampleSize = in.readInt();
535                        settings.lostApSampleSize = in.readInt();
536                        settings.unchangedSampleSize = in.readInt();
537                        settings.minApsBreachingThreshold = in.readInt();
538                        settings.periodInMs = in.readInt();
539                        int len = in.readInt();
540                        settings.bssidInfos = new BssidInfo[len];
541                        for (int i = 0; i < len; i++) {
542                            BssidInfo info = new BssidInfo();
543                            info.bssid = in.readString();
544                            info.low = in.readInt();
545                            info.high = in.readInt();
546                            info.frequencyHint = in.readInt();
547                            settings.bssidInfos[i] = info;
548                        }
549                        return settings;
550                    }
551
552                    public WifiChangeSettings[] newArray(int size) {
553                        return new WifiChangeSettings[size];
554                    }
555                };
556
557    }
558
559    /** configure WifiChange detection
560     * @param rssiSampleSize number of samples used for RSSI averaging
561     * @param lostApSampleSize number of samples to confirm an access point's loss
562     * @param unchangedSampleSize number of samples to confirm there are no changes
563     * @param minApsBreachingThreshold minimum number of access points that need to be
564     *                                 out of range to detect WifiChange
565     * @param periodInMs indicates period of scan to find changes
566     * @param bssidInfos access points to watch
567     */
568    public void configureWifiChange(
569            int rssiSampleSize,                             /* sample size for RSSI averaging */
570            int lostApSampleSize,                           /* samples to confirm AP's loss */
571            int unchangedSampleSize,                        /* samples to confirm no change */
572            int minApsBreachingThreshold,                   /* change threshold to trigger event */
573            int periodInMs,                                 /* period of scan */
574            BssidInfo[] bssidInfos                          /* signal thresholds to crosss */
575            )
576    {
577        validateChannel();
578
579        WifiChangeSettings settings = new WifiChangeSettings();
580        settings.rssiSampleSize = rssiSampleSize;
581        settings.lostApSampleSize = lostApSampleSize;
582        settings.unchangedSampleSize = unchangedSampleSize;
583        settings.minApsBreachingThreshold = minApsBreachingThreshold;
584        settings.periodInMs = periodInMs;
585        settings.bssidInfos = bssidInfos;
586
587        configureWifiChange(settings);
588    }
589
590    /**
591     * interface to get wifi change events on; use this on {@link #startTrackingWifiChange}
592     */
593    public interface WifiChangeListener extends ActionListener {
594        /** indicates that changes were detected in wifi environment
595         * @param results indicate the access points that exhibited change
596         */
597        public void onChanging(ScanResult[] results);           /* changes are found */
598        /** indicates that no wifi changes are being detected for a while
599         * @param results indicate the access points that are bing monitored for change
600         */
601        public void onQuiescence(ScanResult[] results);         /* changes settled down */
602    }
603
604    /**
605     * track changes in wifi environment
606     * @param listener object to report events on; this object must be unique and must also be
607     *                 provided on {@link #stopTrackingWifiChange}
608     */
609    public void startTrackingWifiChange(WifiChangeListener listener) {
610        validateChannel();
611        sAsyncChannel.sendMessage(CMD_START_TRACKING_CHANGE, 0, putListener(listener));
612    }
613
614    /**
615     * stop tracking changes in wifi environment
616     * @param listener object that was provided to report events on {@link
617     * #stopTrackingWifiChange}
618     */
619    public void stopTrackingWifiChange(WifiChangeListener listener) {
620        validateChannel();
621        sAsyncChannel.sendMessage(CMD_STOP_TRACKING_CHANGE, 0, removeListener(listener));
622    }
623
624    /** @hide */
625    @SystemApi
626    public void configureWifiChange(WifiChangeSettings settings) {
627        validateChannel();
628        sAsyncChannel.sendMessage(CMD_CONFIGURE_WIFI_CHANGE, 0, 0, settings);
629    }
630
631    /** interface to receive hotlist events on; use this on {@link #setHotlist} */
632    public static interface BssidListener extends ActionListener {
633        /** indicates that access points were found by on going scans
634         * @param results list of scan results, one for each access point visible currently
635         */
636        public void onFound(ScanResult[] results);
637    }
638
639    /** @hide */
640    @SystemApi
641    public static class HotlistSettings implements Parcelable {
642        public BssidInfo[] bssidInfos;
643        public int apLostThreshold;
644
645        /** Implement the Parcelable interface {@hide} */
646        public int describeContents() {
647            return 0;
648        }
649
650        /** Implement the Parcelable interface {@hide} */
651        public void writeToParcel(Parcel dest, int flags) {
652            dest.writeInt(apLostThreshold);
653
654            if (bssidInfos != null) {
655                dest.writeInt(bssidInfos.length);
656                for (int i = 0; i < bssidInfos.length; i++) {
657                    BssidInfo info = bssidInfos[i];
658                    dest.writeString(info.bssid);
659                    dest.writeInt(info.low);
660                    dest.writeInt(info.high);
661                    dest.writeInt(info.frequencyHint);
662                }
663            } else {
664                dest.writeInt(0);
665            }
666        }
667
668        /** Implement the Parcelable interface {@hide} */
669        public static final Creator<HotlistSettings> CREATOR =
670                new Creator<HotlistSettings>() {
671                    public HotlistSettings createFromParcel(Parcel in) {
672                        HotlistSettings settings = new HotlistSettings();
673                        settings.apLostThreshold = in.readInt();
674                        int n = in.readInt();
675                        settings.bssidInfos = new BssidInfo[n];
676                        for (int i = 0; i < n; i++) {
677                            BssidInfo info = new BssidInfo();
678                            info.bssid = in.readString();
679                            info.low = in.readInt();
680                            info.high = in.readInt();
681                            info.frequencyHint = in.readInt();
682                            settings.bssidInfos[i] = info;
683                        }
684                        return settings;
685                    }
686
687                    public HotlistSettings[] newArray(int size) {
688                        return new HotlistSettings[size];
689                    }
690                };
691    }
692
693    /**
694     * set interesting access points to find
695     * @param bssidInfos access points of interest
696     * @param apLostThreshold number of scans needed to indicate that AP is lost
697     * @param listener object provided to report events on; this object must be unique and must
698     *                 also be provided on {@link #stopTrackingBssids}
699     */
700    public void startTrackingBssids(BssidInfo[] bssidInfos,
701                                    int apLostThreshold, BssidListener listener) {
702        validateChannel();
703        HotlistSettings settings = new HotlistSettings();
704        settings.bssidInfos = bssidInfos;
705        sAsyncChannel.sendMessage(CMD_SET_HOTLIST, 0, putListener(listener), settings);
706    }
707
708    /**
709     * remove tracking of interesting access points
710     * @param listener same object provided in {@link #startTrackingBssids}
711     */
712    public void stopTrackingBssids(BssidListener listener) {
713        validateChannel();
714        sAsyncChannel.sendMessage(CMD_RESET_HOTLIST, 0, removeListener(listener));
715    }
716
717
718    /* private members and methods */
719
720    private static final String TAG = "WifiScanner";
721    private static final boolean DBG = true;
722
723    /* commands for Wifi Service */
724    private static final int BASE = Protocol.BASE_WIFI_SCANNER;
725
726    /** @hide */
727    public static final int CMD_SCAN                        = BASE + 0;
728    /** @hide */
729    public static final int CMD_START_BACKGROUND_SCAN       = BASE + 2;
730    /** @hide */
731    public static final int CMD_STOP_BACKGROUND_SCAN        = BASE + 3;
732    /** @hide */
733    public static final int CMD_GET_SCAN_RESULTS            = BASE + 4;
734    /** @hide */
735    public static final int CMD_SCAN_RESULT                 = BASE + 5;
736    /** @hide */
737    public static final int CMD_SET_HOTLIST                 = BASE + 6;
738    /** @hide */
739    public static final int CMD_RESET_HOTLIST               = BASE + 7;
740    /** @hide */
741    public static final int CMD_AP_FOUND                    = BASE + 9;
742    /** @hide */
743    public static final int CMD_AP_LOST                     = BASE + 10;
744    /** @hide */
745    public static final int CMD_START_TRACKING_CHANGE       = BASE + 11;
746    /** @hide */
747    public static final int CMD_STOP_TRACKING_CHANGE        = BASE + 12;
748    /** @hide */
749    public static final int CMD_CONFIGURE_WIFI_CHANGE       = BASE + 13;
750    /** @hide */
751    public static final int CMD_WIFI_CHANGE_DETECTED        = BASE + 15;
752    /** @hide */
753    public static final int CMD_WIFI_CHANGES_STABILIZED     = BASE + 16;
754    /** @hide */
755    public static final int CMD_OP_SUCCEEDED                = BASE + 17;
756    /** @hide */
757    public static final int CMD_OP_FAILED                   = BASE + 18;
758    /** @hide */
759    public static final int CMD_PERIOD_CHANGED              = BASE + 19;
760    /** @hide */
761    public static final int CMD_FULL_SCAN_RESULT            = BASE + 20;
762    /** @hide */
763    public static final int CMD_START_SINGLE_SCAN           = BASE + 21;
764    /** @hide */
765    public static final int CMD_STOP_SINGLE_SCAN            = BASE + 22;
766    /** @hide */
767    public static final int CMD_SINGLE_SCAN_COMPLETED       = BASE + 23;
768
769    private Context mContext;
770    private IWifiScanner mService;
771
772    private static final int INVALID_KEY = 0;
773    private static int sListenerKey = 1;
774
775    private static final SparseArray sListenerMap = new SparseArray();
776    private static final Object sListenerMapLock = new Object();
777
778    private static AsyncChannel sAsyncChannel;
779    private static CountDownLatch sConnected;
780
781    private static final Object sThreadRefLock = new Object();
782    private static int sThreadRefCount;
783    private static HandlerThread sHandlerThread;
784
785    /**
786     * Create a new WifiScanner instance.
787     * Applications will almost always want to use
788     * {@link android.content.Context#getSystemService Context.getSystemService()} to retrieve
789     * the standard {@link android.content.Context#WIFI_SERVICE Context.WIFI_SERVICE}.
790     * @param context the application context
791     * @param service the Binder interface
792     * @hide
793     */
794    public WifiScanner(Context context, IWifiScanner service) {
795        mContext = context;
796        mService = service;
797        init();
798    }
799
800    private void init() {
801        synchronized (sThreadRefLock) {
802            if (++sThreadRefCount == 1) {
803                Messenger messenger = null;
804                try {
805                    messenger = mService.getMessenger();
806                } catch (RemoteException e) {
807                    /* do nothing */
808                } catch (SecurityException e) {
809                    /* do nothing */
810                }
811
812                if (messenger == null) {
813                    sAsyncChannel = null;
814                    return;
815                }
816
817                sHandlerThread = new HandlerThread("WifiScanner");
818                sAsyncChannel = new AsyncChannel();
819                sConnected = new CountDownLatch(1);
820
821                sHandlerThread.start();
822                Handler handler = new ServiceHandler(sHandlerThread.getLooper());
823                sAsyncChannel.connect(mContext, handler, messenger);
824                try {
825                    sConnected.await();
826                } catch (InterruptedException e) {
827                    Log.e(TAG, "interrupted wait at init");
828                }
829            }
830        }
831    }
832
833    private void validateChannel() {
834        if (sAsyncChannel == null) throw new IllegalStateException(
835                "No permission to access and change wifi or a bad initialization");
836    }
837
838    private static int putListener(Object listener) {
839        if (listener == null) return INVALID_KEY;
840        int key;
841        synchronized (sListenerMapLock) {
842            do {
843                key = sListenerKey++;
844            } while (key == INVALID_KEY);
845            sListenerMap.put(key, listener);
846        }
847        return key;
848    }
849
850    private static Object getListener(int key) {
851        if (key == INVALID_KEY) return null;
852        synchronized (sListenerMapLock) {
853            Object listener = sListenerMap.get(key);
854            return listener;
855        }
856    }
857
858    private static int getListenerKey(Object listener) {
859        if (listener == null) return INVALID_KEY;
860        synchronized (sListenerMapLock) {
861            int index = sListenerMap.indexOfValue(listener);
862            if (index == -1) {
863                return INVALID_KEY;
864            } else {
865                return sListenerMap.keyAt(index);
866            }
867        }
868    }
869
870    private static Object removeListener(int key) {
871        if (key == INVALID_KEY) return null;
872        synchronized (sListenerMapLock) {
873            Object listener = sListenerMap.get(key);
874            sListenerMap.remove(key);
875            return listener;
876        }
877    }
878
879    private static int removeListener(Object listener) {
880        int key = getListenerKey(listener);
881        if (key == INVALID_KEY) return key;
882        synchronized (sListenerMapLock) {
883            sListenerMap.remove(key);
884            return key;
885        }
886    }
887
888    /** @hide */
889    public static class OperationResult implements Parcelable {
890        public int reason;
891        public String description;
892
893        public OperationResult(int reason, String description) {
894            this.reason = reason;
895            this.description = description;
896        }
897
898        /** Implement the Parcelable interface {@hide} */
899        public int describeContents() {
900            return 0;
901        }
902
903        /** Implement the Parcelable interface {@hide} */
904        public void writeToParcel(Parcel dest, int flags) {
905            dest.writeInt(reason);
906            dest.writeString(description);
907        }
908
909        /** Implement the Parcelable interface {@hide} */
910        public static final Creator<OperationResult> CREATOR =
911                new Creator<OperationResult>() {
912                    public OperationResult createFromParcel(Parcel in) {
913                        int reason = in.readInt();
914                        String description = in.readString();
915                        return new OperationResult(reason, description);
916                    }
917
918                    public OperationResult[] newArray(int size) {
919                        return new OperationResult[size];
920                    }
921                };
922    }
923
924    private static class ServiceHandler extends Handler {
925        ServiceHandler(Looper looper) {
926            super(looper);
927        }
928        @Override
929        public void handleMessage(Message msg) {
930            switch (msg.what) {
931                case AsyncChannel.CMD_CHANNEL_HALF_CONNECTED:
932                    if (msg.arg1 == AsyncChannel.STATUS_SUCCESSFUL) {
933                        sAsyncChannel.sendMessage(AsyncChannel.CMD_CHANNEL_FULL_CONNECTION);
934                    } else {
935                        Log.e(TAG, "Failed to set up channel connection");
936                        // This will cause all further async API calls on the WifiManager
937                        // to fail and throw an exception
938                        sAsyncChannel = null;
939                    }
940                    sConnected.countDown();
941                    return;
942                case AsyncChannel.CMD_CHANNEL_FULLY_CONNECTED:
943                    return;
944                case AsyncChannel.CMD_CHANNEL_DISCONNECTED:
945                    Log.e(TAG, "Channel connection lost");
946                    // This will cause all further async API calls on the WifiManager
947                    // to fail and throw an exception
948                    sAsyncChannel = null;
949                    getLooper().quit();
950                    return;
951            }
952
953            Object listener = getListener(msg.arg2);
954
955            if (listener == null) {
956                if (DBG) Log.d(TAG, "invalid listener key = " + msg.arg2);
957                return;
958            } else {
959                if (DBG) Log.d(TAG, "listener key = " + msg.arg2);
960            }
961
962            switch (msg.what) {
963                    /* ActionListeners grouped together */
964                case CMD_OP_SUCCEEDED :
965                    ((ActionListener) listener).onSuccess();
966                    break;
967                case CMD_OP_FAILED : {
968                        OperationResult result = (OperationResult)msg.obj;
969                        ((ActionListener) listener).onFailure(result.reason, result.description);
970                        removeListener(msg.arg2);
971                    }
972                    break;
973                case CMD_SCAN_RESULT :
974                    ((ScanListener) listener).onResults(
975                            ((ParcelableScanData) msg.obj).getResults());
976                    return;
977                case CMD_FULL_SCAN_RESULT :
978                    ScanResult result = (ScanResult) msg.obj;
979                    ((ScanListener) listener).onFullResult(result);
980                    return;
981                case CMD_PERIOD_CHANGED:
982                    ((ScanListener) listener).onPeriodChanged(msg.arg1);
983                    return;
984                case CMD_AP_FOUND:
985                    ((BssidListener) listener).onFound(
986                            ((ParcelableScanResults) msg.obj).getResults());
987                    return;
988                case CMD_WIFI_CHANGE_DETECTED:
989                    ((WifiChangeListener) listener).onChanging(
990                            ((ParcelableScanResults) msg.obj).getResults());
991                   return;
992                case CMD_WIFI_CHANGES_STABILIZED:
993                    ((WifiChangeListener) listener).onQuiescence(
994                            ((ParcelableScanResults) msg.obj).getResults());
995                    return;
996                case CMD_SINGLE_SCAN_COMPLETED:
997                    Log.d(TAG, "removing listener for single scan");
998                    removeListener(msg.arg2);
999                    break;
1000                default:
1001                    if (DBG) Log.d(TAG, "Ignoring message " + msg.what);
1002                    return;
1003            }
1004        }
1005    }
1006}
1007