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