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