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