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