WifiScanner.java revision d24427fabda5ff0fda6fea40bed588680573388f
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.os.WorkSource;
31import android.util.Log;
32import android.util.SparseArray;
33
34import com.android.internal.annotations.VisibleForTesting;
35import com.android.internal.util.AsyncChannel;
36import com.android.internal.util.Preconditions;
37import com.android.internal.util.Protocol;
38
39import java.util.List;
40import java.util.concurrent.CountDownLatch;
41
42
43/**
44 * This class provides a way to scan the Wifi universe around the device
45 * Get an instance of this class by calling
46 * {@link android.content.Context#getSystemService(String) Context.getSystemService(Context
47 * .WIFI_SCANNING_SERVICE)}.
48 * @hide
49 */
50@SystemApi
51public class WifiScanner {
52
53    /** no band specified; use channel list instead */
54    public static final int WIFI_BAND_UNSPECIFIED = 0;      /* not specified */
55
56    /** 2.4 GHz band */
57    public static final int WIFI_BAND_24_GHZ = 1;           /* 2.4 GHz band */
58    /** 5 GHz band excluding DFS channels */
59    public static final int WIFI_BAND_5_GHZ = 2;            /* 5 GHz band without DFS channels */
60    /** DFS channels from 5 GHz band only */
61    public static final int WIFI_BAND_5_GHZ_DFS_ONLY  = 4;  /* 5 GHz band with DFS channels */
62    /** 5 GHz band including DFS channels */
63    public static final int WIFI_BAND_5_GHZ_WITH_DFS  = 6;  /* 5 GHz band with DFS channels */
64    /** Both 2.4 GHz band and 5 GHz band; no DFS channels */
65    public static final int WIFI_BAND_BOTH = 3;             /* both bands without DFS channels */
66    /** Both 2.4 GHz band and 5 GHz band; with DFS channels */
67    public static final int WIFI_BAND_BOTH_WITH_DFS = 7;    /* both bands with DFS channels */
68
69    /** Minimum supported scanning period */
70    public static final int MIN_SCAN_PERIOD_MS = 1000;      /* minimum supported period */
71    /** Maximum supported scanning period */
72    public static final int MAX_SCAN_PERIOD_MS = 1024000;   /* maximum supported period */
73
74    /** No Error */
75    public static final int REASON_SUCCEEDED = 0;
76    /** Unknown error */
77    public static final int REASON_UNSPECIFIED = -1;
78    /** Invalid listener */
79    public static final int REASON_INVALID_LISTENER = -2;
80    /** Invalid request */
81    public static final int REASON_INVALID_REQUEST = -3;
82    /** Invalid request */
83    public static final int REASON_NOT_AUTHORIZED = -4;
84    /** An outstanding request with the same listener hasn't finished yet. */
85    public static final int REASON_DUPLICATE_REQEUST = -5;
86
87    /** @hide */
88    public static final String GET_AVAILABLE_CHANNELS_EXTRA = "Channels";
89
90    /**
91     * Generic action callback invocation interface
92     *  @hide
93     */
94    @SystemApi
95    public static interface ActionListener {
96        public void onSuccess();
97        public void onFailure(int reason, String description);
98    }
99
100    /**
101     * gives you all the possible channels; channel is specified as an
102     * integer with frequency in MHz i.e. channel 1 is 2412
103     * @hide
104     */
105    public List<Integer> getAvailableChannels(int band) {
106        try {
107            Bundle bundle =  mService.getAvailableChannels(band);
108            return bundle.getIntegerArrayList(GET_AVAILABLE_CHANNELS_EXTRA);
109        } catch (RemoteException e) {
110            return null;
111        }
112    }
113
114    /**
115     * provides channel specification for scanning
116     */
117    public static class ChannelSpec {
118        /**
119         * channel frequency in MHz; for example channel 1 is specified as 2412
120         */
121        public int frequency;
122        /**
123         * if true, scan this channel in passive fashion.
124         * This flag is ignored on DFS channel specification.
125         * @hide
126         */
127        public boolean passive;                                    /* ignored on DFS channels */
128        /**
129         * how long to dwell on this channel
130         * @hide
131         */
132        public int dwellTimeMS;                                    /* not supported for now */
133
134        /**
135         * default constructor for channel spec
136         */
137        public ChannelSpec(int frequency) {
138            this.frequency = frequency;
139            passive = false;
140            dwellTimeMS = 0;
141        }
142    }
143
144    /**
145     * reports {@link ScanListener#onResults} when underlying buffers are full
146     * this is simply the lack of the {@link #REPORT_EVENT_AFTER_EACH_SCAN} flag
147     * @deprecated
148     */
149    @Deprecated
150    public static final int REPORT_EVENT_AFTER_BUFFER_FULL = 0;
151    /**
152     * reports {@link ScanListener#onResults} after each scan
153     */
154    public static final int REPORT_EVENT_AFTER_EACH_SCAN = (1 << 0);
155    /**
156     * reports {@link ScanListener#onFullResult} whenever each beacon is discovered
157     */
158    public static final int REPORT_EVENT_FULL_SCAN_RESULT = (1 << 1);
159    /**
160     * Do not place scans in the chip's scan history buffer
161     */
162    public static final int REPORT_EVENT_NO_BATCH = (1 << 2);
163
164    /**
165     * scan configuration parameters to be sent to {@link #startBackgroundScan}
166     */
167    public static class ScanSettings implements Parcelable {
168
169        /** one of the WIFI_BAND values */
170        public int band;
171        /** list of channels; used when band is set to WIFI_BAND_UNSPECIFIED */
172        public ChannelSpec[] channels;
173        /**
174         * list of networkId's of hidden networks to scan for.
175         * These Id's should correspond to the wpa_supplicant's networkId's and will be used
176         * in connectivity scans using wpa_supplicant.
177         * {@hide}
178         * */
179        public int[] hiddenNetworkIds;
180        /** period of background scan; in millisecond, 0 => single shot scan */
181        public int periodInMs;
182        /** must have a valid REPORT_EVENT value */
183        public int reportEvents;
184        /** defines number of bssids to cache from each scan */
185        public int numBssidsPerScan;
186        /**
187         * defines number of scans to cache; use it with REPORT_EVENT_AFTER_BUFFER_FULL
188         * to wake up at fixed interval
189         */
190        public int maxScansToCache;
191        /**
192         * if maxPeriodInMs is non zero or different than period, then this bucket is
193         * a truncated binary exponential backoff bucket and the scan period will grow
194         * exponentially as per formula: actual_period(N) = period * (2 ^ (N/stepCount))
195         * to maxPeriodInMs
196         */
197        public int maxPeriodInMs;
198        /**
199         * for truncated binary exponential back off bucket, number of scans to perform
200         * for a given period
201         */
202        public int stepCount;
203        /**
204         * Flag to indicate if the scan settings are targeted for PNO scan.
205         * {@hide}
206         */
207        public boolean isPnoScan;
208
209        /** Implement the Parcelable interface {@hide} */
210        public int describeContents() {
211            return 0;
212        }
213
214        /** Implement the Parcelable interface {@hide} */
215        public void writeToParcel(Parcel dest, int flags) {
216            dest.writeInt(band);
217            dest.writeInt(periodInMs);
218            dest.writeInt(reportEvents);
219            dest.writeInt(numBssidsPerScan);
220            dest.writeInt(maxScansToCache);
221            dest.writeInt(maxPeriodInMs);
222            dest.writeInt(stepCount);
223            dest.writeInt(isPnoScan ? 1 : 0);
224            if (channels != null) {
225                dest.writeInt(channels.length);
226                for (int i = 0; i < channels.length; i++) {
227                    dest.writeInt(channels[i].frequency);
228                    dest.writeInt(channels[i].dwellTimeMS);
229                    dest.writeInt(channels[i].passive ? 1 : 0);
230                }
231            } else {
232                dest.writeInt(0);
233            }
234            dest.writeIntArray(hiddenNetworkIds);
235        }
236
237        /** Implement the Parcelable interface {@hide} */
238        public static final Creator<ScanSettings> CREATOR =
239                new Creator<ScanSettings>() {
240                    public ScanSettings createFromParcel(Parcel in) {
241                        ScanSettings settings = new ScanSettings();
242                        settings.band = in.readInt();
243                        settings.periodInMs = in.readInt();
244                        settings.reportEvents = in.readInt();
245                        settings.numBssidsPerScan = in.readInt();
246                        settings.maxScansToCache = in.readInt();
247                        settings.maxPeriodInMs = in.readInt();
248                        settings.stepCount = in.readInt();
249                        settings.isPnoScan = in.readInt() == 1;
250                        int num_channels = in.readInt();
251                        settings.channels = new ChannelSpec[num_channels];
252                        for (int i = 0; i < num_channels; i++) {
253                            int frequency = in.readInt();
254                            ChannelSpec spec = new ChannelSpec(frequency);
255                            spec.dwellTimeMS = in.readInt();
256                            spec.passive = in.readInt() == 1;
257                            settings.channels[i] = spec;
258                        }
259                        settings.hiddenNetworkIds = in.createIntArray();
260                        return settings;
261                    }
262
263                    public ScanSettings[] newArray(int size) {
264                        return new ScanSettings[size];
265                    }
266                };
267
268    }
269
270    /**
271     * all the information garnered from a single scan
272     */
273    public static class ScanData implements Parcelable {
274        /** scan identifier */
275        private int mId;
276        /** additional information about scan
277         * 0 => no special issues encountered in the scan
278         * non-zero => scan was truncated, so results may not be complete
279         */
280        private int mFlags;
281        /**
282         * Indicates the buckets that were scanned to generate these results.
283         * This is not relevant to WifiScanner API users and is used internally.
284         * {@hide}
285         */
286        private int mBucketsScanned;
287        /** all scan results discovered in this scan, sorted by timestamp in ascending order */
288        private ScanResult mResults[];
289
290        ScanData() {}
291
292        public ScanData(int id, int flags, ScanResult[] results) {
293            mId = id;
294            mFlags = flags;
295            mResults = results;
296        }
297
298        /** {@hide} */
299        public ScanData(int id, int flags, int bucketsScanned, ScanResult[] results) {
300            mId = id;
301            mFlags = flags;
302            mBucketsScanned = bucketsScanned;
303            mResults = results;
304        }
305
306        public ScanData(ScanData s) {
307            mId = s.mId;
308            mFlags = s.mFlags;
309            mBucketsScanned = s.mBucketsScanned;
310            mResults = new ScanResult[s.mResults.length];
311            for (int i = 0; i < s.mResults.length; i++) {
312                ScanResult result = s.mResults[i];
313                ScanResult newResult = new ScanResult(result);
314                mResults[i] = newResult;
315            }
316        }
317
318        public int getId() {
319            return mId;
320        }
321
322        public int getFlags() {
323            return mFlags;
324        }
325
326        /** {@hide} */
327        public int getBucketsScanned() {
328            return mBucketsScanned;
329        }
330
331        public ScanResult[] getResults() {
332            return mResults;
333        }
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            if (mResults != null) {
343                dest.writeInt(mId);
344                dest.writeInt(mFlags);
345                dest.writeInt(mBucketsScanned);
346                dest.writeInt(mResults.length);
347                for (int i = 0; i < mResults.length; i++) {
348                    ScanResult result = mResults[i];
349                    result.writeToParcel(dest, flags);
350                }
351            } else {
352                dest.writeInt(0);
353            }
354        }
355
356        /** Implement the Parcelable interface {@hide} */
357        public static final Creator<ScanData> CREATOR =
358                new Creator<ScanData>() {
359                    public ScanData createFromParcel(Parcel in) {
360                        int id = in.readInt();
361                        int flags = in.readInt();
362                        int bucketsScanned = in.readInt();
363                        int n = in.readInt();
364                        ScanResult results[] = new ScanResult[n];
365                        for (int i = 0; i < n; i++) {
366                            results[i] = ScanResult.CREATOR.createFromParcel(in);
367                        }
368                        return new ScanData(id, flags, bucketsScanned, results);
369                    }
370
371                    public ScanData[] newArray(int size) {
372                        return new ScanData[size];
373                    }
374                };
375    }
376
377    public static class ParcelableScanData implements Parcelable {
378
379        public ScanData mResults[];
380
381        public ParcelableScanData(ScanData[] results) {
382            mResults = results;
383        }
384
385        public ScanData[] getResults() {
386            return mResults;
387        }
388
389        /** Implement the Parcelable interface {@hide} */
390        public int describeContents() {
391            return 0;
392        }
393
394        /** Implement the Parcelable interface {@hide} */
395        public void writeToParcel(Parcel dest, int flags) {
396            if (mResults != null) {
397                dest.writeInt(mResults.length);
398                for (int i = 0; i < mResults.length; i++) {
399                    ScanData result = mResults[i];
400                    result.writeToParcel(dest, flags);
401                }
402            } else {
403                dest.writeInt(0);
404            }
405        }
406
407        /** Implement the Parcelable interface {@hide} */
408        public static final Creator<ParcelableScanData> CREATOR =
409                new Creator<ParcelableScanData>() {
410                    public ParcelableScanData createFromParcel(Parcel in) {
411                        int n = in.readInt();
412                        ScanData results[] = new ScanData[n];
413                        for (int i = 0; i < n; i++) {
414                            results[i] = ScanData.CREATOR.createFromParcel(in);
415                        }
416                        return new ParcelableScanData(results);
417                    }
418
419                    public ParcelableScanData[] newArray(int size) {
420                        return new ParcelableScanData[size];
421                    }
422                };
423    }
424
425    public static class ParcelableScanResults implements Parcelable {
426
427        public ScanResult mResults[];
428
429        public ParcelableScanResults(ScanResult[] results) {
430            mResults = results;
431        }
432
433        public ScanResult[] getResults() {
434            return mResults;
435        }
436
437        /** Implement the Parcelable interface {@hide} */
438        public int describeContents() {
439            return 0;
440        }
441
442        /** Implement the Parcelable interface {@hide} */
443        public void writeToParcel(Parcel dest, int flags) {
444            if (mResults != null) {
445                dest.writeInt(mResults.length);
446                for (int i = 0; i < mResults.length; i++) {
447                    ScanResult result = mResults[i];
448                    result.writeToParcel(dest, flags);
449                }
450            } else {
451                dest.writeInt(0);
452            }
453        }
454
455        /** Implement the Parcelable interface {@hide} */
456        public static final Creator<ParcelableScanResults> CREATOR =
457                new Creator<ParcelableScanResults>() {
458                    public ParcelableScanResults createFromParcel(Parcel in) {
459                        int n = in.readInt();
460                        ScanResult results[] = new ScanResult[n];
461                        for (int i = 0; i < n; i++) {
462                            results[i] = ScanResult.CREATOR.createFromParcel(in);
463                        }
464                        return new ParcelableScanResults(results);
465                    }
466
467                    public ParcelableScanResults[] newArray(int size) {
468                        return new ParcelableScanResults[size];
469                    }
470                };
471    }
472
473    /** {@hide} */
474    public static final String PNO_PARAMS_PNO_SETTINGS_KEY = "PnoSettings";
475    /** {@hide} */
476    public static final String PNO_PARAMS_SCAN_SETTINGS_KEY = "ScanSettings";
477    /**
478     * PNO scan configuration parameters to be sent to {@link #startPnoScan}.
479     * Note: This structure needs to be in sync with |wifi_epno_params| struct in gscan HAL API.
480     * {@hide}
481     */
482    public static class PnoSettings implements Parcelable {
483        /**
484         * Pno network to be added to the PNO scan filtering.
485         * {@hide}
486         */
487        public static class PnoNetwork {
488            /*
489             * Pno flags bitmask to be set in {@link #PnoNetwork.flags}
490             */
491            /** Whether directed scan needs to be performed (for hidden SSIDs) */
492            public static final byte FLAG_DIRECTED_SCAN = (1 << 0);
493            /** Whether PNO event shall be triggered if the network is found on A band */
494            public static final byte FLAG_A_BAND = (1 << 1);
495            /** Whether PNO event shall be triggered if the network is found on G band */
496            public static final byte FLAG_G_BAND = (1 << 2);
497            /**
498             * Whether strict matching is required
499             * If required then the firmware must store the network's SSID and not just a hash
500             */
501            public static final byte FLAG_STRICT_MATCH = (1 << 3);
502            /**
503             * If this SSID should be considered the same network as the currently connected
504             * one for scoring.
505             */
506            public static final byte FLAG_SAME_NETWORK = (1 << 4);
507
508            /*
509             * Code for matching the beacon AUTH IE - additional codes. Bitmask to be set in
510             * {@link #PnoNetwork.authBitField}
511             */
512            /** Open Network */
513            public static final byte AUTH_CODE_OPEN = (1 << 0);
514            /** WPA_PSK or WPA2PSK */
515            public static final byte AUTH_CODE_PSK = (1 << 1);
516            /** any EAPOL */
517            public static final byte AUTH_CODE_EAPOL = (1 << 2);
518
519            /** SSID of the network */
520            public String ssid;
521            /** Network ID in wpa_supplicant */
522            public int networkId;
523            /** Assigned priority for the network */
524            public int priority;
525            /** Bitmask of the FLAG_XXX */
526            public byte flags;
527            /** Bitmask of the ATUH_XXX */
528            public byte authBitField;
529
530            /**
531             * default constructor for PnoNetwork
532             */
533            public PnoNetwork(String ssid) {
534                this.ssid = ssid;
535                flags = 0;
536                authBitField = 0;
537            }
538        }
539
540        /** Connected vs Disconnected PNO flag {@hide} */
541        public boolean isConnected;
542        /** Minimum 5GHz RSSI for a BSSID to be considered */
543        public int min5GHzRssi;
544        /** Minimum 2.4GHz RSSI for a BSSID to be considered */
545        public int min24GHzRssi;
546        /** Maximum score that a network can have before bonuses */
547        public int initialScoreMax;
548        /**
549         *  Only report when there is a network's score this much higher
550         *  than the current connection.
551         */
552        public int currentConnectionBonus;
553        /** score bonus for all networks with the same network flag */
554        public int sameNetworkBonus;
555        /** score bonus for networks that are not open */
556        public int secureBonus;
557        /** 5GHz RSSI score bonus (applied to all 5GHz networks) */
558        public int band5GHzBonus;
559        /** Pno Network filter list */
560        public PnoNetwork[] networkList;
561
562        /** Implement the Parcelable interface {@hide} */
563        public int describeContents() {
564            return 0;
565        }
566
567        /** Implement the Parcelable interface {@hide} */
568        public void writeToParcel(Parcel dest, int flags) {
569            dest.writeInt(isConnected ? 1 : 0);
570            dest.writeInt(min5GHzRssi);
571            dest.writeInt(min24GHzRssi);
572            dest.writeInt(initialScoreMax);
573            dest.writeInt(currentConnectionBonus);
574            dest.writeInt(sameNetworkBonus);
575            dest.writeInt(secureBonus);
576            dest.writeInt(band5GHzBonus);
577            if (networkList != null) {
578                dest.writeInt(networkList.length);
579                for (int i = 0; i < networkList.length; i++) {
580                    dest.writeString(networkList[i].ssid);
581                    dest.writeInt(networkList[i].networkId);
582                    dest.writeInt(networkList[i].priority);
583                    dest.writeByte(networkList[i].flags);
584                    dest.writeByte(networkList[i].authBitField);
585                }
586            } else {
587                dest.writeInt(0);
588            }
589        }
590
591        /** Implement the Parcelable interface {@hide} */
592        public static final Creator<PnoSettings> CREATOR =
593                new Creator<PnoSettings>() {
594                    public PnoSettings createFromParcel(Parcel in) {
595                        PnoSettings settings = new PnoSettings();
596                        settings.isConnected = in.readInt() == 1;
597                        settings.min5GHzRssi = in.readInt();
598                        settings.min24GHzRssi = in.readInt();
599                        settings.initialScoreMax = in.readInt();
600                        settings.currentConnectionBonus = in.readInt();
601                        settings.sameNetworkBonus = in.readInt();
602                        settings.secureBonus = in.readInt();
603                        settings.band5GHzBonus = in.readInt();
604                        int numNetworks = in.readInt();
605                        settings.networkList = new PnoNetwork[numNetworks];
606                        for (int i = 0; i < numNetworks; i++) {
607                            String ssid = in.readString();
608                            PnoNetwork network = new PnoNetwork(ssid);
609                            network.networkId = in.readInt();
610                            network.priority = in.readInt();
611                            network.flags = in.readByte();
612                            network.authBitField = in.readByte();
613                            settings.networkList[i] = network;
614                        }
615                        return settings;
616                    }
617
618                    public PnoSettings[] newArray(int size) {
619                        return new PnoSettings[size];
620                    }
621                };
622
623    }
624
625    /**
626     * interface to get scan events on; specify this on {@link #startBackgroundScan} or
627     * {@link #startScan}
628     */
629    public interface ScanListener extends ActionListener {
630        /**
631         * Framework co-ordinates scans across multiple apps; so it may not give exactly the
632         * same period requested. If period of a scan is changed; it is reported by this event.
633         */
634        public void onPeriodChanged(int periodInMs);
635        /**
636         * reports results retrieved from background scan and single shot scans
637         */
638        public void onResults(ScanData[] results);
639        /**
640         * reports full scan result for each access point found in scan
641         */
642        public void onFullResult(ScanResult fullScanResult);
643    }
644
645    /**
646     * interface to get PNO scan events on; specify this on {@link #startDisconnectedPnoScan} and
647     * {@link #startConnectedPnoScan}.
648     * {@hide}
649     */
650    public interface PnoScanListener extends ScanListener {
651        /**
652         * Invoked when one of the PNO networks are found in scan results.
653         */
654        void onPnoNetworkFound(ScanResult[] results);
655    }
656
657    /** start wifi scan in background
658     * @param settings specifies various parameters for the scan; for more information look at
659     * {@link ScanSettings}
660     * @param listener specifies the object to report events to. This object is also treated as a
661     *                 key for this scan, and must also be specified to cancel the scan. Multiple
662     *                 scans should also not share this object.
663     */
664    public void startBackgroundScan(ScanSettings settings, ScanListener listener) {
665        startBackgroundScan(settings, listener, null);
666    }
667
668    /** start wifi scan in background
669     * @param settings specifies various parameters for the scan; for more information look at
670     * {@link ScanSettings}
671     * @param workSource WorkSource to blame for power usage
672     * @param listener specifies the object to report events to. This object is also treated as a
673     *                 key for this scan, and must also be specified to cancel the scan. Multiple
674     *                 scans should also not share this object.
675     */
676    public void startBackgroundScan(ScanSettings settings, ScanListener listener,
677            WorkSource workSource) {
678        Preconditions.checkNotNull(listener, "listener cannot be null");
679        int key = addListener(listener);
680        if (key == INVALID_KEY) return;
681        validateChannel();
682        sAsyncChannel.sendMessage(CMD_START_BACKGROUND_SCAN, 0, key, settings);
683    }
684
685    /**
686     * stop an ongoing wifi scan
687     * @param listener specifies which scan to cancel; must be same object as passed in {@link
688     *  #startBackgroundScan}
689     */
690    public void stopBackgroundScan(ScanListener listener) {
691        Preconditions.checkNotNull(listener, "listener cannot be null");
692        int key = removeListener(listener);
693        if (key == INVALID_KEY) return;
694        validateChannel();
695        sAsyncChannel.sendMessage(CMD_STOP_BACKGROUND_SCAN, 0, key);
696    }
697    /**
698     * reports currently available scan results on appropriate listeners
699     * @return true if all scan results were reported correctly
700     */
701    public boolean getScanResults() {
702        validateChannel();
703        Message reply = sAsyncChannel.sendMessageSynchronously(CMD_GET_SCAN_RESULTS, 0);
704        return reply.what == CMD_OP_SUCCEEDED;
705    }
706
707    /**
708     * starts a single scan and reports results asynchronously
709     * @param settings specifies various parameters for the scan; for more information look at
710     * {@link ScanSettings}
711     * @param listener specifies the object to report events to. This object is also treated as a
712     *                 key for this scan, and must also be specified to cancel the scan. Multiple
713     *                 scans should also not share this object.
714     */
715    public void startScan(ScanSettings settings, ScanListener listener) {
716        startScan(settings, listener, null);
717    }
718
719    /**
720     * starts a single scan and reports results asynchronously
721     * @param settings specifies various parameters for the scan; for more information look at
722     * {@link ScanSettings}
723     * @param workSource WorkSource to blame for power usage
724     * @param listener specifies the object to report events to. This object is also treated as a
725     *                 key for this scan, and must also be specified to cancel the scan. Multiple
726     *                 scans should also not share this object.
727     */
728    public void startScan(ScanSettings settings, ScanListener listener, WorkSource workSource) {
729        Preconditions.checkNotNull(listener, "listener cannot be null");
730        int key = addListener(listener);
731        if (key == INVALID_KEY) return;
732        validateChannel();
733        sAsyncChannel.sendMessage(CMD_START_SINGLE_SCAN, 0, key, settings);
734    }
735
736    /**
737     * stops an ongoing single shot scan; only useful after {@link #startScan} if onResults()
738     * hasn't been called on the listener, ignored otherwise
739     * @param listener
740     */
741    public void stopScan(ScanListener listener) {
742        Preconditions.checkNotNull(listener, "listener cannot be null");
743        int key = removeListener(listener);
744        if (key == INVALID_KEY) return;
745        validateChannel();
746        sAsyncChannel.sendMessage(CMD_STOP_SINGLE_SCAN, 0, key);
747    }
748
749    private void startPnoScan(ScanSettings scanSettings, PnoSettings pnoSettings, int key) {
750        // Bundle up both the settings and send it across.
751        Bundle pnoParams = new Bundle();
752        if (pnoParams == null) return;
753        // Set the PNO scan flag.
754        scanSettings.isPnoScan = true;
755        pnoParams.putParcelable(PNO_PARAMS_SCAN_SETTINGS_KEY, scanSettings);
756        pnoParams.putParcelable(PNO_PARAMS_PNO_SETTINGS_KEY, pnoSettings);
757        sAsyncChannel.sendMessage(CMD_START_PNO_SCAN, 0, key, pnoParams);
758    }
759    /**
760     * Start wifi connected PNO scan
761     * @param scanSettings specifies various parameters for the scan; for more information look at
762     * {@link ScanSettings}
763     * @param pnoSettings specifies various parameters for PNO; for more information look at
764     * {@link PnoSettings}
765     * @param listener specifies the object to report events to. This object is also treated as a
766     *                 key for this scan, and must also be specified to cancel the scan. Multiple
767     *                 scans should also not share this object.
768     * {@hide}
769     */
770    public void startConnectedPnoScan(ScanSettings scanSettings, PnoSettings pnoSettings,
771            PnoScanListener listener) {
772        Preconditions.checkNotNull(listener, "listener cannot be null");
773        Preconditions.checkNotNull(pnoSettings, "pnoSettings cannot be null");
774        int key = addListener(listener);
775        if (key == INVALID_KEY) return;
776        validateChannel();
777        pnoSettings.isConnected = true;
778        startPnoScan(scanSettings, pnoSettings, key);
779    }
780    /**
781     * Start wifi disconnected PNO scan
782     * @param scanSettings specifies various parameters for the scan; for more information look at
783     * {@link ScanSettings}
784     * @param pnoSettings specifies various parameters for PNO; for more information look at
785     * {@link PnoSettings}
786     * @param listener specifies the object to report events to. This object is also treated as a
787     *                 key for this scan, and must also be specified to cancel the scan. Multiple
788     *                 scans should also not share this object.
789     * {@hide}
790     */
791    public void startDisconnectedPnoScan(ScanSettings scanSettings, PnoSettings pnoSettings,
792            PnoScanListener listener) {
793        Preconditions.checkNotNull(listener, "listener cannot be null");
794        Preconditions.checkNotNull(pnoSettings, "pnoSettings cannot be null");
795        int key = addListener(listener);
796        if (key == INVALID_KEY) return;
797        validateChannel();
798        pnoSettings.isConnected = false;
799        startPnoScan(scanSettings, pnoSettings, key);
800    }
801    /**
802     * Stop an ongoing wifi PNO scan
803     * @param pnoSettings specifies various parameters for PNO; for more information look at
804     * {@link PnoSettings}
805     * @param listener specifies which scan to cancel; must be same object as passed in {@link
806     *  #startPnoScan}
807     * TODO(rpius): Check if we can remove pnoSettings param in stop.
808     * {@hide}
809     */
810    public void stopPnoScan(PnoSettings pnoSettings, ScanListener listener) {
811        Preconditions.checkNotNull(listener, "listener cannot be null");
812        int key = removeListener(listener);
813        if (key == INVALID_KEY) return;
814        validateChannel();
815        sAsyncChannel.sendMessage(CMD_STOP_PNO_SCAN, 0, key, pnoSettings);
816    }
817
818    /** specifies information about an access point of interest */
819    public static class BssidInfo {
820        /** bssid of the access point; in XX:XX:XX:XX:XX:XX format */
821        public String bssid;
822        /** low signal strength threshold; more information at {@link ScanResult#level} */
823        public int low;                                            /* minimum RSSI */
824        /** high signal threshold; more information at {@link ScanResult#level} */
825        public int high;                                           /* maximum RSSI */
826        /** channel frequency (in KHz) where you may find this BSSID */
827        public int frequencyHint;
828    }
829
830    /** @hide */
831    @SystemApi
832    public static class WifiChangeSettings implements Parcelable {
833        public int rssiSampleSize;                          /* sample size for RSSI averaging */
834        public int lostApSampleSize;                        /* samples to confirm AP's loss */
835        public int unchangedSampleSize;                     /* samples to confirm no change */
836        public int minApsBreachingThreshold;                /* change threshold to trigger event */
837        public int periodInMs;                              /* scan period in millisecond */
838        public BssidInfo[] bssidInfos;
839
840        /** Implement the Parcelable interface {@hide} */
841        public int describeContents() {
842            return 0;
843        }
844
845        /** Implement the Parcelable interface {@hide} */
846        public void writeToParcel(Parcel dest, int flags) {
847            dest.writeInt(rssiSampleSize);
848            dest.writeInt(lostApSampleSize);
849            dest.writeInt(unchangedSampleSize);
850            dest.writeInt(minApsBreachingThreshold);
851            dest.writeInt(periodInMs);
852            if (bssidInfos != null) {
853                dest.writeInt(bssidInfos.length);
854                for (int i = 0; i < bssidInfos.length; i++) {
855                    BssidInfo info = bssidInfos[i];
856                    dest.writeString(info.bssid);
857                    dest.writeInt(info.low);
858                    dest.writeInt(info.high);
859                    dest.writeInt(info.frequencyHint);
860                }
861            } else {
862                dest.writeInt(0);
863            }
864        }
865
866        /** Implement the Parcelable interface {@hide} */
867        public static final Creator<WifiChangeSettings> CREATOR =
868                new Creator<WifiChangeSettings>() {
869                    public WifiChangeSettings createFromParcel(Parcel in) {
870                        WifiChangeSettings settings = new WifiChangeSettings();
871                        settings.rssiSampleSize = in.readInt();
872                        settings.lostApSampleSize = in.readInt();
873                        settings.unchangedSampleSize = in.readInt();
874                        settings.minApsBreachingThreshold = in.readInt();
875                        settings.periodInMs = in.readInt();
876                        int len = in.readInt();
877                        settings.bssidInfos = new BssidInfo[len];
878                        for (int i = 0; i < len; i++) {
879                            BssidInfo info = new BssidInfo();
880                            info.bssid = in.readString();
881                            info.low = in.readInt();
882                            info.high = in.readInt();
883                            info.frequencyHint = in.readInt();
884                            settings.bssidInfos[i] = info;
885                        }
886                        return settings;
887                    }
888
889                    public WifiChangeSettings[] newArray(int size) {
890                        return new WifiChangeSettings[size];
891                    }
892                };
893
894    }
895
896    /** configure WifiChange detection
897     * @param rssiSampleSize number of samples used for RSSI averaging
898     * @param lostApSampleSize number of samples to confirm an access point's loss
899     * @param unchangedSampleSize number of samples to confirm there are no changes
900     * @param minApsBreachingThreshold minimum number of access points that need to be
901     *                                 out of range to detect WifiChange
902     * @param periodInMs indicates period of scan to find changes
903     * @param bssidInfos access points to watch
904     */
905    public void configureWifiChange(
906            int rssiSampleSize,                             /* sample size for RSSI averaging */
907            int lostApSampleSize,                           /* samples to confirm AP's loss */
908            int unchangedSampleSize,                        /* samples to confirm no change */
909            int minApsBreachingThreshold,                   /* change threshold to trigger event */
910            int periodInMs,                                 /* period of scan */
911            BssidInfo[] bssidInfos                          /* signal thresholds to crosss */
912            )
913    {
914        validateChannel();
915
916        WifiChangeSettings settings = new WifiChangeSettings();
917        settings.rssiSampleSize = rssiSampleSize;
918        settings.lostApSampleSize = lostApSampleSize;
919        settings.unchangedSampleSize = unchangedSampleSize;
920        settings.minApsBreachingThreshold = minApsBreachingThreshold;
921        settings.periodInMs = periodInMs;
922        settings.bssidInfos = bssidInfos;
923
924        configureWifiChange(settings);
925    }
926
927    /**
928     * interface to get wifi change events on; use this on {@link #startTrackingWifiChange}
929     */
930    public interface WifiChangeListener extends ActionListener {
931        /** indicates that changes were detected in wifi environment
932         * @param results indicate the access points that exhibited change
933         */
934        public void onChanging(ScanResult[] results);           /* changes are found */
935        /** indicates that no wifi changes are being detected for a while
936         * @param results indicate the access points that are bing monitored for change
937         */
938        public void onQuiescence(ScanResult[] results);         /* changes settled down */
939    }
940
941    /**
942     * track changes in wifi environment
943     * @param listener object to report events on; this object must be unique and must also be
944     *                 provided on {@link #stopTrackingWifiChange}
945     */
946    public void startTrackingWifiChange(WifiChangeListener listener) {
947        Preconditions.checkNotNull(listener, "listener cannot be null");
948        int key = addListener(listener);
949        if (key == INVALID_KEY) return;
950        validateChannel();
951        sAsyncChannel.sendMessage(CMD_START_TRACKING_CHANGE, 0, key);
952    }
953
954    /**
955     * stop tracking changes in wifi environment
956     * @param listener object that was provided to report events on {@link
957     * #stopTrackingWifiChange}
958     */
959    public void stopTrackingWifiChange(WifiChangeListener listener) {
960        int key = removeListener(listener);
961        if (key == INVALID_KEY) return;
962        validateChannel();
963        sAsyncChannel.sendMessage(CMD_STOP_TRACKING_CHANGE, 0, key);
964    }
965
966    /** @hide */
967    @SystemApi
968    public void configureWifiChange(WifiChangeSettings settings) {
969        validateChannel();
970        sAsyncChannel.sendMessage(CMD_CONFIGURE_WIFI_CHANGE, 0, 0, settings);
971    }
972
973    /** interface to receive hotlist events on; use this on {@link #setHotlist} */
974    public static interface BssidListener extends ActionListener {
975        /** indicates that access points were found by on going scans
976         * @param results list of scan results, one for each access point visible currently
977         */
978        public void onFound(ScanResult[] results);
979        /** indicates that access points were missed by on going scans
980         * @param results list of scan results, for each access point that is not visible anymore
981         */
982        public void onLost(ScanResult[] results);
983    }
984
985    /** @hide */
986    @SystemApi
987    public static class HotlistSettings implements Parcelable {
988        public BssidInfo[] bssidInfos;
989        public int apLostThreshold;
990
991        /** Implement the Parcelable interface {@hide} */
992        public int describeContents() {
993            return 0;
994        }
995
996        /** Implement the Parcelable interface {@hide} */
997        public void writeToParcel(Parcel dest, int flags) {
998            dest.writeInt(apLostThreshold);
999
1000            if (bssidInfos != null) {
1001                dest.writeInt(bssidInfos.length);
1002                for (int i = 0; i < bssidInfos.length; i++) {
1003                    BssidInfo info = bssidInfos[i];
1004                    dest.writeString(info.bssid);
1005                    dest.writeInt(info.low);
1006                    dest.writeInt(info.high);
1007                    dest.writeInt(info.frequencyHint);
1008                }
1009            } else {
1010                dest.writeInt(0);
1011            }
1012        }
1013
1014        /** Implement the Parcelable interface {@hide} */
1015        public static final Creator<HotlistSettings> CREATOR =
1016                new Creator<HotlistSettings>() {
1017                    public HotlistSettings createFromParcel(Parcel in) {
1018                        HotlistSettings settings = new HotlistSettings();
1019                        settings.apLostThreshold = in.readInt();
1020                        int n = in.readInt();
1021                        settings.bssidInfos = new BssidInfo[n];
1022                        for (int i = 0; i < n; i++) {
1023                            BssidInfo info = new BssidInfo();
1024                            info.bssid = in.readString();
1025                            info.low = in.readInt();
1026                            info.high = in.readInt();
1027                            info.frequencyHint = in.readInt();
1028                            settings.bssidInfos[i] = info;
1029                        }
1030                        return settings;
1031                    }
1032
1033                    public HotlistSettings[] newArray(int size) {
1034                        return new HotlistSettings[size];
1035                    }
1036                };
1037    }
1038
1039    /**
1040     * set interesting access points to find
1041     * @param bssidInfos access points of interest
1042     * @param apLostThreshold number of scans needed to indicate that AP is lost
1043     * @param listener object provided to report events on; this object must be unique and must
1044     *                 also be provided on {@link #stopTrackingBssids}
1045     */
1046    public void startTrackingBssids(BssidInfo[] bssidInfos,
1047                                    int apLostThreshold, BssidListener listener) {
1048        Preconditions.checkNotNull(listener, "listener cannot be null");
1049        int key = addListener(listener);
1050        if (key == INVALID_KEY) return;
1051        validateChannel();
1052        HotlistSettings settings = new HotlistSettings();
1053        settings.bssidInfos = bssidInfos;
1054        settings.apLostThreshold = apLostThreshold;
1055        sAsyncChannel.sendMessage(CMD_SET_HOTLIST, 0, key, settings);
1056    }
1057
1058    /**
1059     * remove tracking of interesting access points
1060     * @param listener same object provided in {@link #startTrackingBssids}
1061     */
1062    public void stopTrackingBssids(BssidListener listener) {
1063        Preconditions.checkNotNull(listener, "listener cannot be null");
1064        int key = removeListener(listener);
1065        if (key == INVALID_KEY) return;
1066        validateChannel();
1067        sAsyncChannel.sendMessage(CMD_RESET_HOTLIST, 0, key);
1068    }
1069
1070
1071    /* private members and methods */
1072
1073    private static final String TAG = "WifiScanner";
1074    private static final boolean DBG = false;
1075
1076    /* commands for Wifi Service */
1077    private static final int BASE = Protocol.BASE_WIFI_SCANNER;
1078
1079    /** @hide */
1080    public static final int CMD_SCAN                        = BASE + 0;
1081    /** @hide */
1082    public static final int CMD_START_BACKGROUND_SCAN       = BASE + 2;
1083    /** @hide */
1084    public static final int CMD_STOP_BACKGROUND_SCAN        = BASE + 3;
1085    /** @hide */
1086    public static final int CMD_GET_SCAN_RESULTS            = BASE + 4;
1087    /** @hide */
1088    public static final int CMD_SCAN_RESULT                 = BASE + 5;
1089    /** @hide */
1090    public static final int CMD_SET_HOTLIST                 = BASE + 6;
1091    /** @hide */
1092    public static final int CMD_RESET_HOTLIST               = BASE + 7;
1093    /** @hide */
1094    public static final int CMD_AP_FOUND                    = BASE + 9;
1095    /** @hide */
1096    public static final int CMD_AP_LOST                     = BASE + 10;
1097    /** @hide */
1098    public static final int CMD_START_TRACKING_CHANGE       = BASE + 11;
1099    /** @hide */
1100    public static final int CMD_STOP_TRACKING_CHANGE        = BASE + 12;
1101    /** @hide */
1102    public static final int CMD_CONFIGURE_WIFI_CHANGE       = BASE + 13;
1103    /** @hide */
1104    public static final int CMD_WIFI_CHANGE_DETECTED        = BASE + 15;
1105    /** @hide */
1106    public static final int CMD_WIFI_CHANGES_STABILIZED     = BASE + 16;
1107    /** @hide */
1108    public static final int CMD_OP_SUCCEEDED                = BASE + 17;
1109    /** @hide */
1110    public static final int CMD_OP_FAILED                   = BASE + 18;
1111    /** @hide */
1112    public static final int CMD_PERIOD_CHANGED              = BASE + 19;
1113    /** @hide */
1114    public static final int CMD_FULL_SCAN_RESULT            = BASE + 20;
1115    /** @hide */
1116    public static final int CMD_START_SINGLE_SCAN           = BASE + 21;
1117    /** @hide */
1118    public static final int CMD_STOP_SINGLE_SCAN            = BASE + 22;
1119    /** @hide */
1120    public static final int CMD_SINGLE_SCAN_COMPLETED       = BASE + 23;
1121    /** @hide */
1122    public static final int CMD_START_PNO_SCAN              = BASE + 24;
1123    /** @hide */
1124    public static final int CMD_STOP_PNO_SCAN               = BASE + 25;
1125    /** @hide */
1126    public static final int CMD_PNO_NETWORK_FOUND           = BASE + 26;
1127
1128    private Context mContext;
1129    private IWifiScanner mService;
1130
1131    private static final int INVALID_KEY = 0;
1132    private static int sListenerKey = 1;
1133
1134    private static final SparseArray sListenerMap = new SparseArray();
1135    private static final Object sListenerMapLock = new Object();
1136
1137    private static AsyncChannel sAsyncChannel;
1138    private static CountDownLatch sConnected;
1139
1140    private static final Object sThreadRefLock = new Object();
1141    private static int sThreadRefCount;
1142    private static Handler sInternalHandler;
1143
1144    /**
1145     * Create a new WifiScanner instance.
1146     * Applications will almost always want to use
1147     * {@link android.content.Context#getSystemService Context.getSystemService()} to retrieve
1148     * the standard {@link android.content.Context#WIFI_SERVICE Context.WIFI_SERVICE}.
1149     * @param context the application context
1150     * @param service the Binder interface
1151     * @hide
1152     */
1153    public WifiScanner(Context context, IWifiScanner service) {
1154        this(context, service, null, true);
1155    }
1156
1157    /**
1158     * Create a new WifiScanner instance.
1159     *
1160     * @param context The application context.
1161     * @param service The IWifiScanner Binder interface
1162     * @param looper Looper for running WifiScanner operations. If null, a handler thread will be
1163     *          created for running WifiScanner operations.
1164     * @param waitForConnection If true, this will not return until a connection to Wifi Scanner
1165     *          service is established.
1166     * @hide
1167     */
1168    @VisibleForTesting
1169    public WifiScanner(Context context, IWifiScanner service, Looper looper,
1170            boolean waitForConnection) {
1171        mContext = context;
1172        mService = service;
1173        init(looper, waitForConnection);
1174    }
1175
1176    private void init(Looper looper, boolean waitForConnection) {
1177        synchronized (sThreadRefLock) {
1178            if (++sThreadRefCount == 1) {
1179                Messenger messenger = null;
1180                try {
1181                    messenger = mService.getMessenger();
1182                } catch (RemoteException e) {
1183                    /* do nothing */
1184                } catch (SecurityException e) {
1185                    /* do nothing */
1186                }
1187
1188                if (messenger == null) {
1189                    sAsyncChannel = null;
1190                    return;
1191                }
1192
1193                sAsyncChannel = new AsyncChannel();
1194                sConnected = new CountDownLatch(1);
1195
1196                if (looper == null) {
1197                    HandlerThread thread = new HandlerThread("WifiScanner");
1198                    thread.start();
1199                    sInternalHandler = new ServiceHandler(thread.getLooper());
1200                } else {
1201                    sInternalHandler = new ServiceHandler(looper);
1202                }
1203                sAsyncChannel.connect(mContext, sInternalHandler, messenger);
1204                if (waitForConnection) {
1205                    try {
1206                        sConnected.await();
1207                    } catch (InterruptedException e) {
1208                        Log.e(TAG, "interrupted wait at init");
1209                    }
1210                }
1211            }
1212        }
1213    }
1214
1215    private void validateChannel() {
1216        if (sAsyncChannel == null) throw new IllegalStateException(
1217                "No permission to access and change wifi or a bad initialization");
1218    }
1219
1220    // Add a listener into listener map. If the listener already exists, return INVALID_KEY and
1221    // send an error message to internal handler; Otherwise add the listener to the listener map and
1222    // return the key of the listener.
1223    private int addListener(ActionListener listener) {
1224        synchronized (sListenerMap) {
1225            boolean keyExists = (getListenerKey(listener) != INVALID_KEY);
1226            // Note we need to put the listener into listener map even if it's a duplicate as the
1227            // internal handler will need the key to find the listener. In case of duplicates,
1228            // removing duplicate key logic will be handled in internal handler.
1229            int key = putListener(listener);
1230            if (keyExists) {
1231                if (DBG) Log.d(TAG, "listener key already exists");
1232                OperationResult operationResult = new OperationResult(REASON_DUPLICATE_REQEUST,
1233                        "Outstanding request with same key not stopped yet");
1234                Message message = Message.obtain(sInternalHandler, CMD_OP_FAILED, 0, key,
1235                        operationResult);
1236                message.sendToTarget();
1237                return INVALID_KEY;
1238            } else {
1239                return key;
1240            }
1241        }
1242    }
1243
1244    private static int putListener(Object listener) {
1245        if (listener == null) return INVALID_KEY;
1246        int key;
1247        synchronized (sListenerMapLock) {
1248            do {
1249                key = sListenerKey++;
1250            } while (key == INVALID_KEY);
1251            sListenerMap.put(key, listener);
1252        }
1253        return key;
1254    }
1255
1256    private static Object getListener(int key) {
1257        if (key == INVALID_KEY) return null;
1258        synchronized (sListenerMapLock) {
1259            Object listener = sListenerMap.get(key);
1260            return listener;
1261        }
1262    }
1263
1264    private static int getListenerKey(Object listener) {
1265        if (listener == null) return INVALID_KEY;
1266        synchronized (sListenerMapLock) {
1267            int index = sListenerMap.indexOfValue(listener);
1268            if (index == -1) {
1269                return INVALID_KEY;
1270            } else {
1271                return sListenerMap.keyAt(index);
1272            }
1273        }
1274    }
1275
1276    private static Object removeListener(int key) {
1277        if (key == INVALID_KEY) return null;
1278        synchronized (sListenerMapLock) {
1279            Object listener = sListenerMap.get(key);
1280            sListenerMap.remove(key);
1281            return listener;
1282        }
1283    }
1284
1285    private static int removeListener(Object listener) {
1286        int key = getListenerKey(listener);
1287        if (key == INVALID_KEY) {
1288            Log.e(TAG, "listener cannot be found");
1289            return key;
1290        }
1291        synchronized (sListenerMapLock) {
1292            sListenerMap.remove(key);
1293            return key;
1294        }
1295    }
1296
1297    /** @hide */
1298    public static class OperationResult implements Parcelable {
1299        public int reason;
1300        public String description;
1301
1302        public OperationResult(int reason, String description) {
1303            this.reason = reason;
1304            this.description = description;
1305        }
1306
1307        /** Implement the Parcelable interface {@hide} */
1308        public int describeContents() {
1309            return 0;
1310        }
1311
1312        /** Implement the Parcelable interface {@hide} */
1313        public void writeToParcel(Parcel dest, int flags) {
1314            dest.writeInt(reason);
1315            dest.writeString(description);
1316        }
1317
1318        /** Implement the Parcelable interface {@hide} */
1319        public static final Creator<OperationResult> CREATOR =
1320                new Creator<OperationResult>() {
1321                    public OperationResult createFromParcel(Parcel in) {
1322                        int reason = in.readInt();
1323                        String description = in.readString();
1324                        return new OperationResult(reason, description);
1325                    }
1326
1327                    public OperationResult[] newArray(int size) {
1328                        return new OperationResult[size];
1329                    }
1330                };
1331    }
1332
1333    private static class ServiceHandler extends Handler {
1334        ServiceHandler(Looper looper) {
1335            super(looper);
1336        }
1337        @Override
1338        public void handleMessage(Message msg) {
1339            switch (msg.what) {
1340                case AsyncChannel.CMD_CHANNEL_HALF_CONNECTED:
1341                    if (msg.arg1 == AsyncChannel.STATUS_SUCCESSFUL) {
1342                        sAsyncChannel.sendMessage(AsyncChannel.CMD_CHANNEL_FULL_CONNECTION);
1343                    } else {
1344                        Log.e(TAG, "Failed to set up channel connection");
1345                        // This will cause all further async API calls on the WifiManager
1346                        // to fail and throw an exception
1347                        sAsyncChannel = null;
1348                    }
1349                    sConnected.countDown();
1350                    return;
1351                case AsyncChannel.CMD_CHANNEL_FULLY_CONNECTED:
1352                    return;
1353                case AsyncChannel.CMD_CHANNEL_DISCONNECTED:
1354                    Log.e(TAG, "Channel connection lost");
1355                    // This will cause all further async API calls on the WifiManager
1356                    // to fail and throw an exception
1357                    sAsyncChannel = null;
1358                    getLooper().quit();
1359                    return;
1360            }
1361
1362            Object listener = getListener(msg.arg2);
1363
1364            if (listener == null) {
1365                if (DBG) Log.d(TAG, "invalid listener key = " + msg.arg2);
1366                return;
1367            } else {
1368                if (DBG) Log.d(TAG, "listener key = " + msg.arg2);
1369            }
1370
1371            switch (msg.what) {
1372                    /* ActionListeners grouped together */
1373                case CMD_OP_SUCCEEDED :
1374                    ((ActionListener) listener).onSuccess();
1375                    break;
1376                case CMD_OP_FAILED : {
1377                        OperationResult result = (OperationResult)msg.obj;
1378                        ((ActionListener) listener).onFailure(result.reason, result.description);
1379                        removeListener(msg.arg2);
1380                    }
1381                    break;
1382                case CMD_SCAN_RESULT :
1383                    ((ScanListener) listener).onResults(
1384                            ((ParcelableScanData) msg.obj).getResults());
1385                    return;
1386                case CMD_FULL_SCAN_RESULT :
1387                    ScanResult result = (ScanResult) msg.obj;
1388                    ((ScanListener) listener).onFullResult(result);
1389                    return;
1390                case CMD_PERIOD_CHANGED:
1391                    ((ScanListener) listener).onPeriodChanged(msg.arg1);
1392                    return;
1393                case CMD_AP_FOUND:
1394                    ((BssidListener) listener).onFound(
1395                            ((ParcelableScanResults) msg.obj).getResults());
1396                    return;
1397                case CMD_AP_LOST:
1398                    ((BssidListener) listener).onLost(
1399                            ((ParcelableScanResults) msg.obj).getResults());
1400                    return;
1401                case CMD_WIFI_CHANGE_DETECTED:
1402                    ((WifiChangeListener) listener).onChanging(
1403                            ((ParcelableScanResults) msg.obj).getResults());
1404                   return;
1405                case CMD_WIFI_CHANGES_STABILIZED:
1406                    ((WifiChangeListener) listener).onQuiescence(
1407                            ((ParcelableScanResults) msg.obj).getResults());
1408                    return;
1409                case CMD_SINGLE_SCAN_COMPLETED:
1410                    if (DBG) Log.d(TAG, "removing listener for single scan");
1411                    removeListener(msg.arg2);
1412                    break;
1413                case CMD_PNO_NETWORK_FOUND:
1414                    ((PnoScanListener) listener).onPnoNetworkFound(
1415                            ((ParcelableScanResults) msg.obj).getResults());
1416                    return;
1417                default:
1418                    if (DBG) Log.d(TAG, "Ignoring message " + msg.what);
1419                    return;
1420            }
1421        }
1422    }
1423}
1424