WifiScanner.java revision c101b1c90b5c2e649c7f4b14c9c40c570a3ece05
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.content.Context;
20import android.os.Handler;
21import android.os.HandlerThread;
22import android.os.Looper;
23import android.os.Message;
24import android.os.Messenger;
25import android.os.Parcel;
26import android.os.Parcelable;
27import android.os.RemoteException;
28import android.util.Log;
29import android.util.SparseArray;
30
31import com.android.internal.util.AsyncChannel;
32import com.android.internal.util.Protocol;
33
34import java.util.List;
35import java.util.concurrent.CountDownLatch;
36
37
38/**
39 * This class provides a way to scan the Wifi universe around the device
40 * Get an instance of this class by calling
41 * {@link android.content.Context#getSystemService(String) Context.getSystemService(Context
42 * .WIFI_SCANNING_SERVICE)}.
43 */
44public class WifiScanner {
45
46    /** no band specified; use channel list instead */
47    public static final int WIFI_BAND_UNSPECIFIED = 0;      /* not specified */
48
49    /** 2.4 GHz band */
50    public static final int WIFI_BAND_24_GHZ = 1;           /* 2.4 GHz band */
51    /** 5 GHz band excluding DFS channels */
52    public static final int WIFI_BAND_5_GHZ = 2;            /* 5 GHz band without DFS channels */
53    /** DFS channels from 5 GHz band only */
54    public static final int WIFI_BAND_5_GHZ_DFS_ONLY  = 4;  /* 5 GHz band with DFS channels */
55    /** 5 GHz band including DFS channels */
56    public static final int WIFI_BAND_5_GHZ_WITH_DFS  = 6;  /* 5 GHz band with DFS channels */
57    /** Both 2.4 GHz band and 5 GHz band; no DFS channels */
58    public static final int WIFI_BAND_BOTH = 3;             /* both bands without DFS channels */
59    /** Both 2.4 GHz band and 5 GHz band; with DFS channels */
60    public static final int WIFI_BAND_BOTH_WITH_DFS = 7;    /* both bands with DFS channels */
61
62    /** Minimum supported scanning period */
63    public static final int MIN_SCAN_PERIOD_MS = 2000;      /* minimum supported period */
64    /** Maximum supported scanning period */
65    public static final int MAX_SCAN_PERIOD_MS = 1024000;   /* maximum supported period */
66
67    /** No Error */
68    public static final int REASON_SUCCEEDED = 0;
69    /** Unknown error */
70    public static final int REASON_UNSPECIFIED = -1;
71    /** Invalid listener */
72    public static final int REASON_INVALID_LISTENER = -2;
73    /** Invalid request */
74    public static final int REASON_INVALID_REQUEST = -3;
75    /** Request conflicts with other scans that may be going on */
76    public static final int REASON_CONFLICTING_REQUEST = -4;
77
78    /**
79     * Generic action callback invocation interface
80     *  @hide
81     */
82    public static interface ActionListener {
83        public void onSuccess(Object result);
84        public void onFailure(int reason, Object exception);
85    }
86
87    /**
88     * gives you all the possible channels; channel is specified as an
89     * integer with frequency in MHz i.e. channel 1 is 2412
90     * @hide
91     */
92    public List<Integer> getAvailableChannels(int band) {
93        return null;
94    }
95
96    /**
97     * provides channel specification for scanning
98     */
99    public static class ChannelSpec {
100        /**
101         * channel frequency in KHz; for example channel 1 is specified as 2412
102         */
103        public int frequency;
104        /**
105         * if true, scan this channel in passive fashion.
106         * This flag is ignored on DFS channel specification.
107         * @hide
108         */
109        public boolean passive;                                    /* ignored on DFS channels */
110        /**
111         * how long to dwell on this channel
112         * @hide
113         */
114        public int dwellTimeMS;                                    /* not supported for now */
115
116        /**
117         * default constructor for channel spec
118         */
119        public ChannelSpec(int frequency) {
120            this.frequency = frequency;
121            passive = false;
122            dwellTimeMS = 0;
123        }
124    }
125
126    /** reports {@link ScanListener#onResults} when underlying buffers are full */
127    public static final int REPORT_EVENT_AFTER_BUFFER_FULL = 0;
128    /** reports {@link ScanListener#onResults} after each scan */
129    public static final int REPORT_EVENT_AFTER_EACH_SCAN = 1;
130    /** reports {@link ScanListener#onFullResult} whenever each beacon is discovered */
131    public static final int REPORT_EVENT_FULL_SCAN_RESULT = 2;
132
133    /**
134     * scan configuration parameters to be sent to {@link #startBackgroundScan}
135     */
136    public static class ScanSettings implements Parcelable {
137
138        /** one of the WIFI_BAND values */
139        public int band;
140        /** list of channels; used when band is set to WIFI_BAND_UNSPECIFIED */
141        public ChannelSpec[] channels;
142        /** period of background scan; in millisecond */
143        public int periodInMs;
144        /** must have a valid REPORT_EVENT value */
145        public int reportEvents;
146
147        /** Implement the Parcelable interface {@hide} */
148        public int describeContents() {
149            return 0;
150        }
151
152        /** Implement the Parcelable interface {@hide} */
153        public void writeToParcel(Parcel dest, int flags) {
154            dest.writeInt(band);
155            dest.writeInt(periodInMs);
156            dest.writeInt(reportEvents);
157            dest.writeInt(channels.length);
158
159            for (int i = 0; i < channels.length; i++) {
160                dest.writeInt(channels[i].frequency);
161                dest.writeInt(channels[i].dwellTimeMS);
162                dest.writeInt(channels[i].passive ? 1 : 0);
163            }
164        }
165
166        /** Implement the Parcelable interface {@hide} */
167        public static final Creator<ScanSettings> CREATOR =
168                new Creator<ScanSettings>() {
169                    public ScanSettings createFromParcel(Parcel in) {
170
171                        ScanSettings settings = new ScanSettings();
172                        settings.band = in.readInt();
173                        settings.periodInMs = in.readInt();
174                        settings.reportEvents = in.readInt();
175                        int num_channels = in.readInt();
176                        settings.channels = new ChannelSpec[num_channels];
177                        for (int i = 0; i < num_channels; i++) {
178                            int frequency = in.readInt();
179
180                            ChannelSpec spec = new ChannelSpec(frequency);
181                            spec.dwellTimeMS = in.readInt();
182                            spec.passive = in.readInt() == 1;
183                            settings.channels[i] = spec;
184                        }
185
186                        return settings;
187                    }
188
189                    public ScanSettings[] newArray(int size) {
190                        return new ScanSettings[size];
191                    }
192                };
193
194    }
195
196    /** information element from beacon */
197    public static class InformationElement {
198        public int id;
199        public byte[] bytes;
200    }
201
202    /** scan result with information elements from beacons */
203    public static class FullScanResult implements Parcelable {
204        public ScanResult result;
205        public InformationElement informationElements[];
206
207        /** Implement the Parcelable interface {@hide} */
208        public int describeContents() {
209            return 0;
210        }
211
212        /** Implement the Parcelable interface {@hide} */
213        public void writeToParcel(Parcel dest, int flags) {
214            result.writeToParcel(dest, flags);
215            dest.writeInt(informationElements.length);
216            for (int i = 0; i < informationElements.length; i++) {
217                dest.writeInt(informationElements[i].id);
218                dest.writeInt(informationElements[i].bytes.length);
219                dest.writeByteArray(informationElements[i].bytes);
220            }
221        }
222
223        /** Implement the Parcelable interface {@hide} */
224        public static final Creator<FullScanResult> CREATOR =
225                new Creator<FullScanResult>() {
226                    public FullScanResult createFromParcel(Parcel in) {
227                        FullScanResult result = new FullScanResult();
228                        result.result = ScanResult.CREATOR.createFromParcel(in);
229                        int n = in.readInt();
230                        result.informationElements = new InformationElement[n];
231                        for (int i = 0; i < n; i++) {
232                            result.informationElements[i] = new InformationElement();
233                            result.informationElements[i].id = in.readInt();
234                            int len = in.readInt();
235                            result.informationElements[i].bytes = new byte[len];
236                            in.readByteArray(result.informationElements[i].bytes);
237                        }
238
239                        return result;
240                    }
241
242                    public FullScanResult[] newArray(int size) {
243                        return new FullScanResult[size];
244                    }
245                };
246    }
247
248    /** @hide */
249    public static class ParcelableScanResults implements Parcelable {
250        public ScanResult mResults[];
251
252        public ParcelableScanResults(ScanResult[] results) {
253            mResults = results;
254        }
255
256        public ScanResult[] getResults() {
257            return mResults;
258        }
259
260        /** Implement the Parcelable interface {@hide} */
261        public int describeContents() {
262            return 0;
263        }
264
265        /** Implement the Parcelable interface {@hide} */
266        public void writeToParcel(Parcel dest, int flags) {
267            dest.writeInt(mResults.length);
268            for (int i = 0; i < mResults.length; i++) {
269                ScanResult result = mResults[i];
270                result.writeToParcel(dest, flags);
271            }
272        }
273
274        /** Implement the Parcelable interface {@hide} */
275        public static final Creator<ParcelableScanResults> CREATOR =
276                new Creator<ParcelableScanResults>() {
277                    public ParcelableScanResults createFromParcel(Parcel in) {
278                        int n = in.readInt();
279                        ScanResult results[] = new ScanResult[n];
280                        for (int i = 0; i < n; i++) {
281                            results[i] = ScanResult.CREATOR.createFromParcel(in);
282                        }
283                        return new ParcelableScanResults(results);
284                    }
285
286                    public ParcelableScanResults[] newArray(int size) {
287                        return new ParcelableScanResults[size];
288                    }
289                };
290    }
291
292    /**
293     * interface to get scan events on; specify this on {@link #startBackgroundScan}
294     */
295    public interface ScanListener extends ActionListener {
296        /**
297         * Framework co-ordinates scans across multiple apps; so it may not give exactly the
298         * same period requested. If period of a scan is changed; it is reported by this event.
299         */
300        public void onPeriodChanged(int periodInMs);
301        /**
302         * reports results retrieved from background scan
303         */
304        public void onResults(ScanResult[] results);
305        /**
306         * reports full scan result for each access point found in scan
307         */
308        public void onFullResult(FullScanResult fullScanResult);
309    }
310
311    /** @hide */
312    public void scan(ScanSettings settings, ScanListener listener) {
313        validateChannel();
314        sAsyncChannel.sendMessage(CMD_SCAN, 0, putListener(listener), settings);
315    }
316
317    /** start wifi scan in background
318     * @param settings specifies various parameters for the scan; for more information look at
319     * {@link ScanSettings}
320     * @param listener specifies the object to report events to. This object is also treated as a
321     *                 key for this scan, and must also be specified to cancel the scan. Multiple
322     *                 scans should also not share this object.
323     */
324    public void startBackgroundScan(ScanSettings settings, ScanListener listener) {
325        validateChannel();
326        sAsyncChannel.sendMessage(CMD_START_BACKGROUND_SCAN, 0, putListener(listener), settings);
327    }
328    /**
329     * stop an ongoing wifi scan
330     * @param listener specifies which scan to cancel; must be same object as passed in {@link
331     *  #startBackgroundScan}
332     */
333    public void stopBackgroundScan(ScanListener listener) {
334        validateChannel();
335        sAsyncChannel.sendMessage(CMD_STOP_BACKGROUND_SCAN, 0, removeListener(listener));
336    }
337    /**
338     * retrieves currently available scan results
339     * @param flush {@code true} means flush all results
340     * @param listener specifies which scan to cancel; must be same object as passed in {@link
341     *                 #startBackgroundScan}
342     */
343    public void retrieveScanResults(boolean flush, ScanListener listener) {
344        validateChannel();
345        sAsyncChannel.sendMessage(CMD_GET_SCAN_RESULTS, 0, getListenerKey(listener));
346    }
347
348    /** specifies information about an access point of interest */
349    public static class HotspotInfo {
350        /** bssid of the access point; in XX:XX:XX:XX:XX:XX format */
351        public String bssid;
352        /** low signal strength threshold; more information at {@link ScanResult#level} */
353        public int low;                                            /* minimum RSSI */
354        /** high signal threshold; more information at {@link ScanResult#level} */
355        public int high;                                           /* maximum RSSI */
356        /** channel frequency (in KHz) where you may find this BSSID */
357        public int frequencyHint;
358    }
359
360    /** @hide */
361    public static class WifiChangeSettings implements Parcelable {
362        public int rssiSampleSize;                          /* sample size for RSSI averaging */
363        public int lostApSampleSize;                        /* samples to confirm AP's loss */
364        public int unchangedSampleSize;                     /* samples to confirm no change */
365        public int minApsBreachingThreshold;                /* change threshold to trigger event */
366        public int periodInMs;                              /* scan period in millisecond */
367        public HotspotInfo[] hotspotInfos;
368
369        /** Implement the Parcelable interface {@hide} */
370        public int describeContents() {
371            return 0;
372        }
373
374        /** Implement the Parcelable interface {@hide} */
375        public void writeToParcel(Parcel dest, int flags) {
376            dest.writeInt(rssiSampleSize);
377            dest.writeInt(lostApSampleSize);
378            dest.writeInt(unchangedSampleSize);
379            dest.writeInt(minApsBreachingThreshold);
380            dest.writeInt(periodInMs);
381            dest.writeInt(hotspotInfos.length);
382            for (int i = 0; i < hotspotInfos.length; i++) {
383                HotspotInfo info = hotspotInfos[i];
384                dest.writeString(info.bssid);
385                dest.writeInt(info.low);
386                dest.writeInt(info.high);
387                dest.writeInt(info.frequencyHint);
388            }
389        }
390
391        /** Implement the Parcelable interface {@hide} */
392        public static final Creator<WifiChangeSettings> CREATOR =
393                new Creator<WifiChangeSettings>() {
394                    public WifiChangeSettings createFromParcel(Parcel in) {
395                        WifiChangeSettings settings = new WifiChangeSettings();
396                        settings.rssiSampleSize = in.readInt();
397                        settings.lostApSampleSize = in.readInt();
398                        settings.unchangedSampleSize = in.readInt();
399                        settings.minApsBreachingThreshold = in.readInt();
400                        settings.periodInMs = in.readInt();
401                        int len = in.readInt();
402                        settings.hotspotInfos = new HotspotInfo[len];
403                        for (int i = 0; i < len; i++) {
404                            HotspotInfo info = new HotspotInfo();
405                            info.bssid = in.readString();
406                            info.low = in.readInt();
407                            info.high = in.readInt();
408                            info.frequencyHint = in.readInt();
409                            settings.hotspotInfos[i] = info;
410                        }
411                        return settings;
412                    }
413
414                    public WifiChangeSettings[] newArray(int size) {
415                        return new WifiChangeSettings[size];
416                    }
417                };
418
419    }
420
421    /** configure WifiChange detection
422     * @param rssiSampleSize number of samples used for RSSI averaging
423     * @param lostApSampleSize number of samples to confirm an access point's loss
424     * @param unchangedSampleSize number of samples to confirm there are no changes
425     * @param minApsBreachingThreshold minimum number of access points that need to be
426     *                                 out of range to detect WifiChange
427     * @param periodInMs indicates period of scan to find changes
428     * @param hotspotInfos access points to watch
429     */
430    public void configureWifiChange(
431            int rssiSampleSize,                             /* sample size for RSSI averaging */
432            int lostApSampleSize,                           /* samples to confirm AP's loss */
433            int unchangedSampleSize,                        /* samples to confirm no change */
434            int minApsBreachingThreshold,                   /* change threshold to trigger event */
435            int periodInMs,                                 /* period of scan */
436            HotspotInfo[] hotspotInfos                      /* signal thresholds to crosss */
437            )
438    {
439        validateChannel();
440
441        WifiChangeSettings settings = new WifiChangeSettings();
442        settings.rssiSampleSize = rssiSampleSize;
443        settings.lostApSampleSize = lostApSampleSize;
444        settings.unchangedSampleSize = unchangedSampleSize;
445        settings.minApsBreachingThreshold = minApsBreachingThreshold;
446        settings.periodInMs = periodInMs;
447        settings.hotspotInfos = hotspotInfos;
448
449        configureWifiChange(settings);
450    }
451
452    /**
453     * interface to get wifi change events on; use this on {@link #startTrackingWifiChange}
454     */
455    public interface WifiChangeListener extends ActionListener {
456        /** indicates that changes were detected in wifi environment
457         * @param results indicate the access points that exhibited change
458         */
459        public void onChanging(ScanResult[] results);           /* changes are found */
460        /** indicates that no wifi changes are being detected for a while
461         * @param results indicate the access points that are bing monitored for change
462         */
463        public void onQuiescence(ScanResult[] results);         /* changes settled down */
464    }
465
466    /**
467     * track changes in wifi environment
468     * @param listener object to report events on; this object must be unique and must also be
469     *                 provided on {@link #stopTrackingWifiChange}
470     */
471    public void startTrackingWifiChange(WifiChangeListener listener) {
472        validateChannel();
473        sAsyncChannel.sendMessage(CMD_START_TRACKING_CHANGE, 0, putListener(listener));
474    }
475
476    /**
477     * stop tracking changes in wifi environment
478     * @param listener object that was provided to report events on {@link
479     * #stopTrackingWifiChange}
480     */
481    public void stopTrackingWifiChange(WifiChangeListener listener) {
482        validateChannel();
483        sAsyncChannel.sendMessage(CMD_STOP_TRACKING_CHANGE, 0, removeListener(listener));
484    }
485
486    /** @hide */
487    public void configureWifiChange(WifiChangeSettings settings) {
488        validateChannel();
489        sAsyncChannel.sendMessage(CMD_CONFIGURE_WIFI_CHANGE, 0, 0, settings);
490    }
491
492    /** interface to receive hotlist events on; use this on {@link #setHotlist} */
493    public static interface HotlistListener extends ActionListener {
494        /** indicates that access points were found by on going scans
495         * @param results list of scan results, one for each access point visible currently
496         */
497        public void onFound(ScanResult[] results);
498    }
499
500    /** @hide */
501    public static class HotlistSettings implements Parcelable {
502        public HotspotInfo[] hotspotInfos;
503        public int apLostThreshold;
504
505        /** Implement the Parcelable interface {@hide} */
506        public int describeContents() {
507            return 0;
508        }
509
510        /** Implement the Parcelable interface {@hide} */
511        public void writeToParcel(Parcel dest, int flags) {
512            dest.writeInt(apLostThreshold);
513            dest.writeInt(hotspotInfos.length);
514            for (int i = 0; i < hotspotInfos.length; i++) {
515                HotspotInfo info = hotspotInfos[i];
516                dest.writeString(info.bssid);
517                dest.writeInt(info.low);
518                dest.writeInt(info.high);
519                dest.writeInt(info.frequencyHint);
520            }
521        }
522
523        /** Implement the Parcelable interface {@hide} */
524        public static final Creator<HotlistSettings> CREATOR =
525                new Creator<HotlistSettings>() {
526                    public HotlistSettings createFromParcel(Parcel in) {
527                        HotlistSettings settings = new HotlistSettings();
528                        settings.apLostThreshold = in.readInt();
529                        int n = in.readInt();
530                        settings.hotspotInfos = new HotspotInfo[n];
531                        for (int i = 0; i < n; i++) {
532                            HotspotInfo info = new HotspotInfo();
533                            info.bssid = in.readString();
534                            info.low = in.readInt();
535                            info.high = in.readInt();
536                            info.frequencyHint = in.readInt();
537                            settings.hotspotInfos[i] = info;
538                        }
539                        return settings;
540                    }
541
542                    public HotlistSettings[] newArray(int size) {
543                        return new HotlistSettings[size];
544                    }
545                };
546    }
547
548    /**
549     * set interesting access points to find
550     * @param hotspots access points of interest
551     * @param apLostThreshold number of scans needed to indicate that AP is lost
552     * @param listener object provided to report events on; this object must be unique and must
553     *                 also be provided on {@link #resetHotlist}
554     */
555    public void setHotlist(HotspotInfo[] hotspots,
556            int apLostThreshold, HotlistListener listener) {
557        validateChannel();
558        HotlistSettings settings = new HotlistSettings();
559        settings.hotspotInfos = hotspots;
560        sAsyncChannel.sendMessage(CMD_SET_HOTLIST, 0, putListener(listener), settings);
561    }
562
563    /**
564     * remove tracking of interesting access points
565     * @param listener same object provided in {@link #setHotlist}
566     */
567    public void resetHotlist(HotlistListener listener) {
568        validateChannel();
569        sAsyncChannel.sendMessage(CMD_RESET_HOTLIST, 0, removeListener(listener));
570    }
571
572
573    /* private members and methods */
574
575    private static final String TAG = "WifiScanner";
576    private static final boolean DBG = true;
577
578    /* commands for Wifi Service */
579    private static final int BASE = Protocol.BASE_WIFI_SCANNER;
580
581    /** @hide */
582    public static final int CMD_SCAN                        = BASE + 0;
583    /** @hide */
584    public static final int CMD_START_BACKGROUND_SCAN       = BASE + 2;
585    /** @hide */
586    public static final int CMD_STOP_BACKGROUND_SCAN        = BASE + 3;
587    /** @hide */
588    public static final int CMD_GET_SCAN_RESULTS            = BASE + 4;
589    /** @hide */
590    public static final int CMD_SCAN_RESULT                 = BASE + 5;
591    /** @hide */
592    public static final int CMD_SET_HOTLIST                 = BASE + 6;
593    /** @hide */
594    public static final int CMD_RESET_HOTLIST               = BASE + 7;
595    /** @hide */
596    public static final int CMD_AP_FOUND                    = BASE + 9;
597    /** @hide */
598    public static final int CMD_AP_LOST                     = BASE + 10;
599    /** @hide */
600    public static final int CMD_START_TRACKING_CHANGE       = BASE + 11;
601    /** @hide */
602    public static final int CMD_STOP_TRACKING_CHANGE        = BASE + 12;
603    /** @hide */
604    public static final int CMD_CONFIGURE_WIFI_CHANGE       = BASE + 13;
605    /** @hide */
606    public static final int CMD_WIFI_CHANGE_DETECTED        = BASE + 15;
607    /** @hide */
608    public static final int CMD_WIFI_CHANGES_STABILIZED     = BASE + 16;
609    /** @hide */
610    public static final int CMD_OP_SUCCEEDED                = BASE + 17;
611    /** @hide */
612    public static final int CMD_OP_FAILED                   = BASE + 18;
613    /** @hide */
614    public static final int CMD_PERIOD_CHANGED              = BASE + 19;
615    /** @hide */
616    public static final int CMD_FULL_SCAN_RESULT            = BASE + 20;
617
618    private Context mContext;
619    private IWifiScanner mService;
620
621    private static final int INVALID_KEY = 0;
622    private static int sListenerKey = 1;
623
624    private static final SparseArray sListenerMap = new SparseArray();
625    private static final Object sListenerMapLock = new Object();
626
627    private static AsyncChannel sAsyncChannel;
628    private static CountDownLatch sConnected;
629
630    private static final Object sThreadRefLock = new Object();
631    private static int sThreadRefCount;
632    private static HandlerThread sHandlerThread;
633
634    /**
635     * Create a new WifiScanner instance.
636     * Applications will almost always want to use
637     * {@link android.content.Context#getSystemService Context.getSystemService()} to retrieve
638     * the standard {@link android.content.Context#WIFI_SERVICE Context.WIFI_SERVICE}.
639     * @param context the application context
640     * @param service the Binder interface
641     * @hide
642     */
643    public WifiScanner(Context context, IWifiScanner service) {
644        mContext = context;
645        mService = service;
646        init();
647    }
648
649    private void init() {
650        synchronized (sThreadRefLock) {
651            if (++sThreadRefCount == 1) {
652                Messenger messenger = null;
653                try {
654                    messenger = mService.getMessenger();
655                } catch (RemoteException e) {
656                    /* do nothing */
657                } catch (SecurityException e) {
658                    /* do nothing */
659                }
660
661                if (messenger == null) {
662                    sAsyncChannel = null;
663                    return;
664                }
665
666                sHandlerThread = new HandlerThread("WifiScanner");
667                sAsyncChannel = new AsyncChannel();
668                sConnected = new CountDownLatch(1);
669
670                sHandlerThread.start();
671                Handler handler = new ServiceHandler(sHandlerThread.getLooper());
672                sAsyncChannel.connect(mContext, handler, messenger);
673                try {
674                    sConnected.await();
675                } catch (InterruptedException e) {
676                    Log.e(TAG, "interrupted wait at init");
677                }
678            }
679        }
680    }
681
682    private void validateChannel() {
683        if (sAsyncChannel == null) throw new IllegalStateException(
684                "No permission to access and change wifi or a bad initialization");
685    }
686
687    private static int putListener(Object listener) {
688        if (listener == null) return INVALID_KEY;
689        int key;
690        synchronized (sListenerMapLock) {
691            do {
692                key = sListenerKey++;
693            } while (key == INVALID_KEY);
694            sListenerMap.put(key, listener);
695        }
696        return key;
697    }
698
699    private static Object getListener(int key) {
700        if (key == INVALID_KEY) return null;
701        synchronized (sListenerMapLock) {
702            Object listener = sListenerMap.get(key);
703            return listener;
704        }
705    }
706
707    private static int getListenerKey(Object listener) {
708        if (listener == null) return INVALID_KEY;
709        synchronized (sListenerMapLock) {
710            int index = sListenerMap.indexOfValue(listener);
711            if (index == -1) {
712                return INVALID_KEY;
713            } else {
714                return sListenerMap.keyAt(index);
715            }
716        }
717    }
718
719    private static Object removeListener(int key) {
720        if (key == INVALID_KEY) return null;
721        synchronized (sListenerMapLock) {
722            Object listener = sListenerMap.get(key);
723            sListenerMap.remove(key);
724            return listener;
725        }
726    }
727
728    private static int removeListener(Object listener) {
729        int key = getListenerKey(listener);
730        if (key == INVALID_KEY) return key;
731        synchronized (sListenerMapLock) {
732            sListenerMap.remove(key);
733            return key;
734        }
735    }
736
737    private static class ServiceHandler extends Handler {
738        ServiceHandler(Looper looper) {
739            super(looper);
740        }
741        @Override
742        public void handleMessage(Message msg) {
743            switch (msg.what) {
744                case AsyncChannel.CMD_CHANNEL_HALF_CONNECTED:
745                    if (msg.arg1 == AsyncChannel.STATUS_SUCCESSFUL) {
746                        sAsyncChannel.sendMessage(AsyncChannel.CMD_CHANNEL_FULL_CONNECTION);
747                    } else {
748                        Log.e(TAG, "Failed to set up channel connection");
749                        // This will cause all further async API calls on the WifiManager
750                        // to fail and throw an exception
751                        sAsyncChannel = null;
752                    }
753                    sConnected.countDown();
754                    return;
755                case AsyncChannel.CMD_CHANNEL_FULLY_CONNECTED:
756                    return;
757                case AsyncChannel.CMD_CHANNEL_DISCONNECTED:
758                    Log.e(TAG, "Channel connection lost");
759                    // This will cause all further async API calls on the WifiManager
760                    // to fail and throw an exception
761                    sAsyncChannel = null;
762                    getLooper().quit();
763                    return;
764            }
765
766            Object listener = getListener(msg.arg2);
767            if (DBG) Log.d(TAG, "listener key = " + msg.arg2);
768
769            switch (msg.what) {
770                    /* ActionListeners grouped together */
771                case CMD_OP_SUCCEEDED :
772                    ((ActionListener) listener).onSuccess(msg.obj);
773                    break;
774                case CMD_OP_FAILED :
775                    ((ActionListener) listener).onFailure(msg.arg1, msg.obj);
776                    removeListener(msg.arg2);
777                    break;
778                case CMD_SCAN_RESULT :
779                    ((ScanListener) listener).onResults(
780                            ((ParcelableScanResults) msg.obj).getResults());
781                    return;
782                case CMD_FULL_SCAN_RESULT :
783                    FullScanResult result = (FullScanResult) msg.obj;
784                    ((ScanListener) listener).onFullResult(result);
785                    return;
786                case CMD_PERIOD_CHANGED:
787                    ((ScanListener) listener).onPeriodChanged(msg.arg1);
788                    return;
789                case CMD_AP_FOUND:
790                    ((HotlistListener) listener).onFound(
791                            ((ParcelableScanResults) msg.obj).getResults());
792                    return;
793                case CMD_WIFI_CHANGE_DETECTED:
794                    ((WifiChangeListener) listener).onChanging(
795                            ((ParcelableScanResults) msg.obj).getResults());
796                   return;
797                case CMD_WIFI_CHANGES_STABILIZED:
798                    ((WifiChangeListener) listener).onQuiescence(
799                            ((ParcelableScanResults) msg.obj).getResults());
800                    return;
801                default:
802                    if (DBG) Log.d(TAG, "Ignoring message " + msg.what);
803                    return;
804            }
805        }
806    }
807}
808