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