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