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