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