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 
17 package android.net.wifi;
18 
19 import android.annotation.SystemApi;
20 import android.content.Context;
21 import android.os.Bundle;
22 import android.os.Handler;
23 import android.os.HandlerThread;
24 import android.os.Looper;
25 import android.os.Message;
26 import android.os.Messenger;
27 import android.os.Parcel;
28 import android.os.Parcelable;
29 import android.os.RemoteException;
30 import android.util.Log;
31 import android.util.SparseArray;
32 
33 import com.android.internal.util.AsyncChannel;
34 import com.android.internal.util.Protocol;
35 
36 import java.util.List;
37 import java.util.concurrent.CountDownLatch;
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
48 public 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 
82     /** @hide */
83     public static final String GET_AVAILABLE_CHANNELS_EXTRA = "Channels";
84 
85     /**
86      * Generic action callback invocation interface
87      *  @hide
88      */
89     @SystemApi
90     public static interface ActionListener {
onSuccess()91         public void onSuccess();
onFailure(int reason, String description)92         public void onFailure(int reason, String description);
93     }
94 
95     /**
96      * gives you all the possible channels; channel is specified as an
97      * integer with frequency in MHz i.e. channel 1 is 2412
98      * @hide
99      */
getAvailableChannels(int band)100     public List<Integer> getAvailableChannels(int band) {
101         try {
102             Bundle bundle =  mService.getAvailableChannels(band);
103             return bundle.getIntegerArrayList(GET_AVAILABLE_CHANNELS_EXTRA);
104         } catch (RemoteException e) {
105             return null;
106         }
107     }
108 
109     /**
110      * provides channel specification for scanning
111      */
112     public static class ChannelSpec {
113         /**
114          * channel frequency in MHz; for example channel 1 is specified as 2412
115          */
116         public int frequency;
117         /**
118          * if true, scan this channel in passive fashion.
119          * This flag is ignored on DFS channel specification.
120          * @hide
121          */
122         public boolean passive;                                    /* ignored on DFS channels */
123         /**
124          * how long to dwell on this channel
125          * @hide
126          */
127         public int dwellTimeMS;                                    /* not supported for now */
128 
129         /**
130          * default constructor for channel spec
131          */
ChannelSpec(int frequency)132         public ChannelSpec(int frequency) {
133             this.frequency = frequency;
134             passive = false;
135             dwellTimeMS = 0;
136         }
137     }
138 
139     /** reports {@link ScanListener#onResults} when underlying buffers are full
140      * @deprecated
141      */
142     @Deprecated
143     public static final int REPORT_EVENT_AFTER_BUFFER_FULL = 0;
144     /** reports {@link ScanListener#onResults} after each scan */
145     public static final int REPORT_EVENT_AFTER_EACH_SCAN = 1;
146     /** reports {@link ScanListener#onFullResult} whenever each beacon is discovered */
147     public static final int REPORT_EVENT_FULL_SCAN_RESULT = 2;
148     /** do not batch */
149     public static final int REPORT_EVENT_NO_BATCH = 4;
150 
151     /**
152      * scan configuration parameters to be sent to {@link #startBackgroundScan}
153      */
154     public static class ScanSettings implements Parcelable {
155 
156         /** one of the WIFI_BAND values */
157         public int band;
158         /** list of channels; used when band is set to WIFI_BAND_UNSPECIFIED */
159         public ChannelSpec[] channels;
160         /** period of background scan; in millisecond, 0 => single shot scan */
161         public int periodInMs;
162         /** must have a valid REPORT_EVENT value */
163         public int reportEvents;
164         /** defines number of bssids to cache from each scan */
165         public int numBssidsPerScan;
166         /**
167          * defines number of scans to cache; use it with REPORT_EVENT_AFTER_BUFFER_FULL
168          * to wake up at fixed interval
169          */
170         public int maxScansToCache;
171 
172         /** Implement the Parcelable interface {@hide} */
describeContents()173         public int describeContents() {
174             return 0;
175         }
176 
177         /** Implement the Parcelable interface {@hide} */
writeToParcel(Parcel dest, int flags)178         public void writeToParcel(Parcel dest, int flags) {
179             dest.writeInt(band);
180             dest.writeInt(periodInMs);
181             dest.writeInt(reportEvents);
182             dest.writeInt(numBssidsPerScan);
183             dest.writeInt(maxScansToCache);
184 
185             if (channels != null) {
186                 dest.writeInt(channels.length);
187 
188                 for (int i = 0; i < channels.length; i++) {
189                     dest.writeInt(channels[i].frequency);
190                     dest.writeInt(channels[i].dwellTimeMS);
191                     dest.writeInt(channels[i].passive ? 1 : 0);
192                 }
193             } else {
194                 dest.writeInt(0);
195             }
196         }
197 
198         /** Implement the Parcelable interface {@hide} */
199         public static final Creator<ScanSettings> CREATOR =
200                 new Creator<ScanSettings>() {
201                     public ScanSettings createFromParcel(Parcel in) {
202 
203                         ScanSettings settings = new ScanSettings();
204                         settings.band = in.readInt();
205                         settings.periodInMs = in.readInt();
206                         settings.reportEvents = in.readInt();
207                         settings.numBssidsPerScan = in.readInt();
208                         settings.maxScansToCache = in.readInt();
209                         int num_channels = in.readInt();
210                         settings.channels = new ChannelSpec[num_channels];
211                         for (int i = 0; i < num_channels; i++) {
212                             int frequency = in.readInt();
213 
214                             ChannelSpec spec = new ChannelSpec(frequency);
215                             spec.dwellTimeMS = in.readInt();
216                             spec.passive = in.readInt() == 1;
217                             settings.channels[i] = spec;
218                         }
219 
220                         return settings;
221                     }
222 
223                     public ScanSettings[] newArray(int size) {
224                         return new ScanSettings[size];
225                     }
226                 };
227 
228     }
229 
230     /**
231      * all the information garnered from a single scan
232      */
233     public static class ScanData implements Parcelable {
234         /** scan identifier */
235         private int mId;
236         /** additional information about scan
237          * 0 => no special issues encountered in the scan
238          * non-zero => scan was truncated, so results may not be complete
239          */
240         private int mFlags;
241         /** all scan results discovered in this scan, sorted by timestamp in ascending order */
242         private ScanResult mResults[];
243 
ScanData()244         ScanData() {}
245 
ScanData(int id, int flags, ScanResult[] results)246         public ScanData(int id, int flags, ScanResult[] results) {
247             mId = id;
248             mFlags = flags;
249             mResults = results;
250         }
251 
ScanData(ScanData s)252         public ScanData(ScanData s) {
253             mId = s.mId;
254             mFlags = s.mFlags;
255             mResults = new ScanResult[s.mResults.length];
256             for (int i = 0; i < s.mResults.length; i++) {
257                 ScanResult result = s.mResults[i];
258                 ScanResult newResult = new ScanResult(result);
259                 mResults[i] = newResult;
260             }
261         }
262 
getId()263         public int getId() {
264             return mId;
265         }
266 
getFlags()267         public int getFlags() {
268             return mFlags;
269         }
270 
getResults()271         public ScanResult[] getResults() {
272             return mResults;
273         }
274 
275         /** Implement the Parcelable interface {@hide} */
describeContents()276         public int describeContents() {
277             return 0;
278         }
279 
280         /** Implement the Parcelable interface {@hide} */
writeToParcel(Parcel dest, int flags)281         public void writeToParcel(Parcel dest, int flags) {
282             if (mResults != null) {
283                 dest.writeInt(mId);
284                 dest.writeInt(mFlags);
285                 dest.writeInt(mResults.length);
286                 for (int i = 0; i < mResults.length; i++) {
287                     ScanResult result = mResults[i];
288                     result.writeToParcel(dest, flags);
289                 }
290             } else {
291                 dest.writeInt(0);
292             }
293         }
294 
295         /** Implement the Parcelable interface {@hide} */
296         public static final Creator<ScanData> CREATOR =
297                 new Creator<ScanData>() {
298                     public ScanData createFromParcel(Parcel in) {
299                         int id = in.readInt();
300                         int flags = in.readInt();
301                         int n = in.readInt();
302                         ScanResult results[] = new ScanResult[n];
303                         for (int i = 0; i < n; i++) {
304                             results[i] = ScanResult.CREATOR.createFromParcel(in);
305                         }
306                         return new ScanData(id, flags, results);
307                     }
308 
309                     public ScanData[] newArray(int size) {
310                         return new ScanData[size];
311                     }
312                 };
313     }
314 
315     public static class ParcelableScanData implements Parcelable {
316 
317         public ScanData mResults[];
318 
ParcelableScanData(ScanData[] results)319         public ParcelableScanData(ScanData[] results) {
320             mResults = results;
321         }
322 
getResults()323         public ScanData[] getResults() {
324             return mResults;
325         }
326 
327         /** Implement the Parcelable interface {@hide} */
describeContents()328         public int describeContents() {
329             return 0;
330         }
331 
332         /** Implement the Parcelable interface {@hide} */
writeToParcel(Parcel dest, int flags)333         public void writeToParcel(Parcel dest, int flags) {
334             if (mResults != null) {
335                 dest.writeInt(mResults.length);
336                 for (int i = 0; i < mResults.length; i++) {
337                     ScanData result = mResults[i];
338                     result.writeToParcel(dest, flags);
339                 }
340             } else {
341                 dest.writeInt(0);
342             }
343         }
344 
345         /** Implement the Parcelable interface {@hide} */
346         public static final Creator<ParcelableScanData> CREATOR =
347                 new Creator<ParcelableScanData>() {
348                     public ParcelableScanData createFromParcel(Parcel in) {
349                         int n = in.readInt();
350                         ScanData results[] = new ScanData[n];
351                         for (int i = 0; i < n; i++) {
352                             results[i] = ScanData.CREATOR.createFromParcel(in);
353                         }
354                         return new ParcelableScanData(results);
355                     }
356 
357                     public ParcelableScanData[] newArray(int size) {
358                         return new ParcelableScanData[size];
359                     }
360                 };
361     }
362 
363     public static class ParcelableScanResults implements Parcelable {
364 
365         public ScanResult mResults[];
366 
ParcelableScanResults(ScanResult[] results)367         public ParcelableScanResults(ScanResult[] results) {
368             mResults = results;
369         }
370 
getResults()371         public ScanResult[] getResults() {
372             return mResults;
373         }
374 
375         /** Implement the Parcelable interface {@hide} */
describeContents()376         public int describeContents() {
377             return 0;
378         }
379 
380         /** Implement the Parcelable interface {@hide} */
writeToParcel(Parcel dest, int flags)381         public void writeToParcel(Parcel dest, int flags) {
382             if (mResults != null) {
383                 dest.writeInt(mResults.length);
384                 for (int i = 0; i < mResults.length; i++) {
385                     ScanResult result = mResults[i];
386                     result.writeToParcel(dest, flags);
387                 }
388             } else {
389                 dest.writeInt(0);
390             }
391         }
392 
393         /** Implement the Parcelable interface {@hide} */
394         public static final Creator<ParcelableScanResults> CREATOR =
395                 new Creator<ParcelableScanResults>() {
396                     public ParcelableScanResults createFromParcel(Parcel in) {
397                         int n = in.readInt();
398                         ScanResult results[] = new ScanResult[n];
399                         for (int i = 0; i < n; i++) {
400                             results[i] = ScanResult.CREATOR.createFromParcel(in);
401                         }
402                         return new ParcelableScanResults(results);
403                     }
404 
405                     public ParcelableScanResults[] newArray(int size) {
406                         return new ParcelableScanResults[size];
407                     }
408                 };
409     }
410 
411     /**
412      * interface to get scan events on; specify this on {@link #startBackgroundScan} or
413      * {@link #startScan}
414      */
415     public interface ScanListener extends ActionListener {
416         /**
417          * Framework co-ordinates scans across multiple apps; so it may not give exactly the
418          * same period requested. If period of a scan is changed; it is reported by this event.
419          */
onPeriodChanged(int periodInMs)420         public void onPeriodChanged(int periodInMs);
421         /**
422          * reports results retrieved from background scan and single shot scans
423          */
onResults(ScanData[] results)424         public void onResults(ScanData[] results);
425         /**
426          * reports full scan result for each access point found in scan
427          */
onFullResult(ScanResult fullScanResult)428         public void onFullResult(ScanResult fullScanResult);
429     }
430 
431     /** start wifi scan in background
432      * @param settings specifies various parameters for the scan; for more information look at
433      * {@link ScanSettings}
434      * @param listener specifies the object to report events to. This object is also treated as a
435      *                 key for this scan, and must also be specified to cancel the scan. Multiple
436      *                 scans should also not share this object.
437      */
startBackgroundScan(ScanSettings settings, ScanListener listener)438     public void startBackgroundScan(ScanSettings settings, ScanListener listener) {
439         validateChannel();
440         sAsyncChannel.sendMessage(CMD_START_BACKGROUND_SCAN, 0, putListener(listener), settings);
441     }
442     /**
443      * stop an ongoing wifi scan
444      * @param listener specifies which scan to cancel; must be same object as passed in {@link
445      *  #startBackgroundScan}
446      */
stopBackgroundScan(ScanListener listener)447     public void stopBackgroundScan(ScanListener listener) {
448         validateChannel();
449         sAsyncChannel.sendMessage(CMD_STOP_BACKGROUND_SCAN, 0, removeListener(listener));
450     }
451     /**
452      * reports currently available scan results on appropriate listeners
453      * @return true if all scan results were reported correctly
454      */
getScanResults()455     public boolean getScanResults() {
456         validateChannel();
457         Message reply = sAsyncChannel.sendMessageSynchronously(CMD_GET_SCAN_RESULTS, 0);
458         return reply.what == CMD_OP_SUCCEEDED;
459     }
460 
461     /**
462      * starts a single scan and reports results asynchronously
463      * @param settings specifies various parameters for the scan; for more information look at
464      * {@link ScanSettings}
465      * @param listener specifies the object to report events to. This object is also treated as a
466      *                 key for this scan, and must also be specified to cancel the scan. Multiple
467      *                 scans should also not share this object.
468      */
startScan(ScanSettings settings, ScanListener listener)469     public void startScan(ScanSettings settings, ScanListener listener) {
470         validateChannel();
471         sAsyncChannel.sendMessage(CMD_START_SINGLE_SCAN, 0, putListener(listener), settings);
472     }
473 
474     /**
475      * stops an ongoing single shot scan; only useful after {@link #startScan} if onResults()
476      * hasn't been called on the listener, ignored otherwise
477      * @param listener
478      */
stopScan(ScanListener listener)479     public void stopScan(ScanListener listener) {
480         validateChannel();
481         sAsyncChannel.sendMessage(CMD_STOP_SINGLE_SCAN, 0, removeListener(listener));
482     }
483 
484     /** specifies information about an access point of interest */
485     public static class BssidInfo {
486         /** bssid of the access point; in XX:XX:XX:XX:XX:XX format */
487         public String bssid;
488         /** low signal strength threshold; more information at {@link ScanResult#level} */
489         public int low;                                            /* minimum RSSI */
490         /** high signal threshold; more information at {@link ScanResult#level} */
491         public int high;                                           /* maximum RSSI */
492         /** channel frequency (in KHz) where you may find this BSSID */
493         public int frequencyHint;
494     }
495 
496     /** @hide */
497     @SystemApi
498     public static class WifiChangeSettings implements Parcelable {
499         public int rssiSampleSize;                          /* sample size for RSSI averaging */
500         public int lostApSampleSize;                        /* samples to confirm AP's loss */
501         public int unchangedSampleSize;                     /* samples to confirm no change */
502         public int minApsBreachingThreshold;                /* change threshold to trigger event */
503         public int periodInMs;                              /* scan period in millisecond */
504         public BssidInfo[] bssidInfos;
505 
506         /** Implement the Parcelable interface {@hide} */
describeContents()507         public int describeContents() {
508             return 0;
509         }
510 
511         /** Implement the Parcelable interface {@hide} */
writeToParcel(Parcel dest, int flags)512         public void writeToParcel(Parcel dest, int flags) {
513             dest.writeInt(rssiSampleSize);
514             dest.writeInt(lostApSampleSize);
515             dest.writeInt(unchangedSampleSize);
516             dest.writeInt(minApsBreachingThreshold);
517             dest.writeInt(periodInMs);
518             if (bssidInfos != null) {
519                 dest.writeInt(bssidInfos.length);
520                 for (int i = 0; i < bssidInfos.length; i++) {
521                     BssidInfo info = bssidInfos[i];
522                     dest.writeString(info.bssid);
523                     dest.writeInt(info.low);
524                     dest.writeInt(info.high);
525                     dest.writeInt(info.frequencyHint);
526                 }
527             } else {
528                 dest.writeInt(0);
529             }
530         }
531 
532         /** Implement the Parcelable interface {@hide} */
533         public static final Creator<WifiChangeSettings> CREATOR =
534                 new Creator<WifiChangeSettings>() {
535                     public WifiChangeSettings createFromParcel(Parcel in) {
536                         WifiChangeSettings settings = new WifiChangeSettings();
537                         settings.rssiSampleSize = in.readInt();
538                         settings.lostApSampleSize = in.readInt();
539                         settings.unchangedSampleSize = in.readInt();
540                         settings.minApsBreachingThreshold = in.readInt();
541                         settings.periodInMs = in.readInt();
542                         int len = in.readInt();
543                         settings.bssidInfos = new BssidInfo[len];
544                         for (int i = 0; i < len; i++) {
545                             BssidInfo info = new BssidInfo();
546                             info.bssid = in.readString();
547                             info.low = in.readInt();
548                             info.high = in.readInt();
549                             info.frequencyHint = in.readInt();
550                             settings.bssidInfos[i] = info;
551                         }
552                         return settings;
553                     }
554 
555                     public WifiChangeSettings[] newArray(int size) {
556                         return new WifiChangeSettings[size];
557                     }
558                 };
559 
560     }
561 
562     /** configure WifiChange detection
563      * @param rssiSampleSize number of samples used for RSSI averaging
564      * @param lostApSampleSize number of samples to confirm an access point's loss
565      * @param unchangedSampleSize number of samples to confirm there are no changes
566      * @param minApsBreachingThreshold minimum number of access points that need to be
567      *                                 out of range to detect WifiChange
568      * @param periodInMs indicates period of scan to find changes
569      * @param bssidInfos access points to watch
570      */
configureWifiChange( int rssiSampleSize, int lostApSampleSize, int unchangedSampleSize, int minApsBreachingThreshold, int periodInMs, BssidInfo[] bssidInfos )571     public void configureWifiChange(
572             int rssiSampleSize,                             /* sample size for RSSI averaging */
573             int lostApSampleSize,                           /* samples to confirm AP's loss */
574             int unchangedSampleSize,                        /* samples to confirm no change */
575             int minApsBreachingThreshold,                   /* change threshold to trigger event */
576             int periodInMs,                                 /* period of scan */
577             BssidInfo[] bssidInfos                          /* signal thresholds to crosss */
578             )
579     {
580         validateChannel();
581 
582         WifiChangeSettings settings = new WifiChangeSettings();
583         settings.rssiSampleSize = rssiSampleSize;
584         settings.lostApSampleSize = lostApSampleSize;
585         settings.unchangedSampleSize = unchangedSampleSize;
586         settings.minApsBreachingThreshold = minApsBreachingThreshold;
587         settings.periodInMs = periodInMs;
588         settings.bssidInfos = bssidInfos;
589 
590         configureWifiChange(settings);
591     }
592 
593     /**
594      * interface to get wifi change events on; use this on {@link #startTrackingWifiChange}
595      */
596     public interface WifiChangeListener extends ActionListener {
597         /** indicates that changes were detected in wifi environment
598          * @param results indicate the access points that exhibited change
599          */
onChanging(ScanResult[] results)600         public void onChanging(ScanResult[] results);           /* changes are found */
601         /** indicates that no wifi changes are being detected for a while
602          * @param results indicate the access points that are bing monitored for change
603          */
onQuiescence(ScanResult[] results)604         public void onQuiescence(ScanResult[] results);         /* changes settled down */
605     }
606 
607     /**
608      * track changes in wifi environment
609      * @param listener object to report events on; this object must be unique and must also be
610      *                 provided on {@link #stopTrackingWifiChange}
611      */
startTrackingWifiChange(WifiChangeListener listener)612     public void startTrackingWifiChange(WifiChangeListener listener) {
613         validateChannel();
614         sAsyncChannel.sendMessage(CMD_START_TRACKING_CHANGE, 0, putListener(listener));
615     }
616 
617     /**
618      * stop tracking changes in wifi environment
619      * @param listener object that was provided to report events on {@link
620      * #stopTrackingWifiChange}
621      */
stopTrackingWifiChange(WifiChangeListener listener)622     public void stopTrackingWifiChange(WifiChangeListener listener) {
623         validateChannel();
624         sAsyncChannel.sendMessage(CMD_STOP_TRACKING_CHANGE, 0, removeListener(listener));
625     }
626 
627     /** @hide */
628     @SystemApi
configureWifiChange(WifiChangeSettings settings)629     public void configureWifiChange(WifiChangeSettings settings) {
630         validateChannel();
631         sAsyncChannel.sendMessage(CMD_CONFIGURE_WIFI_CHANGE, 0, 0, settings);
632     }
633 
634     /** interface to receive hotlist events on; use this on {@link #setHotlist} */
635     public static interface BssidListener extends ActionListener {
636         /** indicates that access points were found by on going scans
637          * @param results list of scan results, one for each access point visible currently
638          */
onFound(ScanResult[] results)639         public void onFound(ScanResult[] results);
640         /** indicates that access points were missed by on going scans
641          * @param results list of scan results, for each access point that is not visible anymore
642          */
onLost(ScanResult[] results)643         public void onLost(ScanResult[] results);
644     }
645 
646     /** @hide */
647     @SystemApi
648     public static class HotlistSettings implements Parcelable {
649         public BssidInfo[] bssidInfos;
650         public int apLostThreshold;
651 
652         /** Implement the Parcelable interface {@hide} */
describeContents()653         public int describeContents() {
654             return 0;
655         }
656 
657         /** Implement the Parcelable interface {@hide} */
writeToParcel(Parcel dest, int flags)658         public void writeToParcel(Parcel dest, int flags) {
659             dest.writeInt(apLostThreshold);
660 
661             if (bssidInfos != null) {
662                 dest.writeInt(bssidInfos.length);
663                 for (int i = 0; i < bssidInfos.length; i++) {
664                     BssidInfo info = bssidInfos[i];
665                     dest.writeString(info.bssid);
666                     dest.writeInt(info.low);
667                     dest.writeInt(info.high);
668                     dest.writeInt(info.frequencyHint);
669                 }
670             } else {
671                 dest.writeInt(0);
672             }
673         }
674 
675         /** Implement the Parcelable interface {@hide} */
676         public static final Creator<HotlistSettings> CREATOR =
677                 new Creator<HotlistSettings>() {
678                     public HotlistSettings createFromParcel(Parcel in) {
679                         HotlistSettings settings = new HotlistSettings();
680                         settings.apLostThreshold = in.readInt();
681                         int n = in.readInt();
682                         settings.bssidInfos = new BssidInfo[n];
683                         for (int i = 0; i < n; i++) {
684                             BssidInfo info = new BssidInfo();
685                             info.bssid = in.readString();
686                             info.low = in.readInt();
687                             info.high = in.readInt();
688                             info.frequencyHint = in.readInt();
689                             settings.bssidInfos[i] = info;
690                         }
691                         return settings;
692                     }
693 
694                     public HotlistSettings[] newArray(int size) {
695                         return new HotlistSettings[size];
696                     }
697                 };
698     }
699 
700     /**
701      * set interesting access points to find
702      * @param bssidInfos access points of interest
703      * @param apLostThreshold number of scans needed to indicate that AP is lost
704      * @param listener object provided to report events on; this object must be unique and must
705      *                 also be provided on {@link #stopTrackingBssids}
706      */
startTrackingBssids(BssidInfo[] bssidInfos, int apLostThreshold, BssidListener listener)707     public void startTrackingBssids(BssidInfo[] bssidInfos,
708                                     int apLostThreshold, BssidListener listener) {
709         validateChannel();
710         HotlistSettings settings = new HotlistSettings();
711         settings.bssidInfos = bssidInfos;
712         sAsyncChannel.sendMessage(CMD_SET_HOTLIST, 0, putListener(listener), settings);
713     }
714 
715     /**
716      * remove tracking of interesting access points
717      * @param listener same object provided in {@link #startTrackingBssids}
718      */
stopTrackingBssids(BssidListener listener)719     public void stopTrackingBssids(BssidListener listener) {
720         validateChannel();
721         sAsyncChannel.sendMessage(CMD_RESET_HOTLIST, 0, removeListener(listener));
722     }
723 
724 
725     /* private members and methods */
726 
727     private static final String TAG = "WifiScanner";
728     private static final boolean DBG = false;
729 
730     /* commands for Wifi Service */
731     private static final int BASE = Protocol.BASE_WIFI_SCANNER;
732 
733     /** @hide */
734     public static final int CMD_SCAN                        = BASE + 0;
735     /** @hide */
736     public static final int CMD_START_BACKGROUND_SCAN       = BASE + 2;
737     /** @hide */
738     public static final int CMD_STOP_BACKGROUND_SCAN        = BASE + 3;
739     /** @hide */
740     public static final int CMD_GET_SCAN_RESULTS            = BASE + 4;
741     /** @hide */
742     public static final int CMD_SCAN_RESULT                 = BASE + 5;
743     /** @hide */
744     public static final int CMD_SET_HOTLIST                 = BASE + 6;
745     /** @hide */
746     public static final int CMD_RESET_HOTLIST               = BASE + 7;
747     /** @hide */
748     public static final int CMD_AP_FOUND                    = BASE + 9;
749     /** @hide */
750     public static final int CMD_AP_LOST                     = BASE + 10;
751     /** @hide */
752     public static final int CMD_START_TRACKING_CHANGE       = BASE + 11;
753     /** @hide */
754     public static final int CMD_STOP_TRACKING_CHANGE        = BASE + 12;
755     /** @hide */
756     public static final int CMD_CONFIGURE_WIFI_CHANGE       = BASE + 13;
757     /** @hide */
758     public static final int CMD_WIFI_CHANGE_DETECTED        = BASE + 15;
759     /** @hide */
760     public static final int CMD_WIFI_CHANGES_STABILIZED     = BASE + 16;
761     /** @hide */
762     public static final int CMD_OP_SUCCEEDED                = BASE + 17;
763     /** @hide */
764     public static final int CMD_OP_FAILED                   = BASE + 18;
765     /** @hide */
766     public static final int CMD_PERIOD_CHANGED              = BASE + 19;
767     /** @hide */
768     public static final int CMD_FULL_SCAN_RESULT            = BASE + 20;
769     /** @hide */
770     public static final int CMD_START_SINGLE_SCAN           = BASE + 21;
771     /** @hide */
772     public static final int CMD_STOP_SINGLE_SCAN            = BASE + 22;
773     /** @hide */
774     public static final int CMD_SINGLE_SCAN_COMPLETED       = BASE + 23;
775 
776     private Context mContext;
777     private IWifiScanner mService;
778 
779     private static final int INVALID_KEY = 0;
780     private static int sListenerKey = 1;
781 
782     private static final SparseArray sListenerMap = new SparseArray();
783     private static final Object sListenerMapLock = new Object();
784 
785     private static AsyncChannel sAsyncChannel;
786     private static CountDownLatch sConnected;
787 
788     private static final Object sThreadRefLock = new Object();
789     private static int sThreadRefCount;
790     private static HandlerThread sHandlerThread;
791 
792     /**
793      * Create a new WifiScanner instance.
794      * Applications will almost always want to use
795      * {@link android.content.Context#getSystemService Context.getSystemService()} to retrieve
796      * the standard {@link android.content.Context#WIFI_SERVICE Context.WIFI_SERVICE}.
797      * @param context the application context
798      * @param service the Binder interface
799      * @hide
800      */
WifiScanner(Context context, IWifiScanner service)801     public WifiScanner(Context context, IWifiScanner service) {
802         mContext = context;
803         mService = service;
804         init();
805     }
806 
init()807     private void init() {
808         synchronized (sThreadRefLock) {
809             if (++sThreadRefCount == 1) {
810                 Messenger messenger = null;
811                 try {
812                     messenger = mService.getMessenger();
813                 } catch (RemoteException e) {
814                     /* do nothing */
815                 } catch (SecurityException e) {
816                     /* do nothing */
817                 }
818 
819                 if (messenger == null) {
820                     sAsyncChannel = null;
821                     return;
822                 }
823 
824                 sHandlerThread = new HandlerThread("WifiScanner");
825                 sAsyncChannel = new AsyncChannel();
826                 sConnected = new CountDownLatch(1);
827 
828                 sHandlerThread.start();
829                 Handler handler = new ServiceHandler(sHandlerThread.getLooper());
830                 sAsyncChannel.connect(mContext, handler, messenger);
831                 try {
832                     sConnected.await();
833                 } catch (InterruptedException e) {
834                     Log.e(TAG, "interrupted wait at init");
835                 }
836             }
837         }
838     }
839 
validateChannel()840     private void validateChannel() {
841         if (sAsyncChannel == null) throw new IllegalStateException(
842                 "No permission to access and change wifi or a bad initialization");
843     }
844 
putListener(Object listener)845     private static int putListener(Object listener) {
846         if (listener == null) return INVALID_KEY;
847         int key;
848         synchronized (sListenerMapLock) {
849             do {
850                 key = sListenerKey++;
851             } while (key == INVALID_KEY);
852             sListenerMap.put(key, listener);
853         }
854         return key;
855     }
856 
getListener(int key)857     private static Object getListener(int key) {
858         if (key == INVALID_KEY) return null;
859         synchronized (sListenerMapLock) {
860             Object listener = sListenerMap.get(key);
861             return listener;
862         }
863     }
864 
getListenerKey(Object listener)865     private static int getListenerKey(Object listener) {
866         if (listener == null) return INVALID_KEY;
867         synchronized (sListenerMapLock) {
868             int index = sListenerMap.indexOfValue(listener);
869             if (index == -1) {
870                 return INVALID_KEY;
871             } else {
872                 return sListenerMap.keyAt(index);
873             }
874         }
875     }
876 
removeListener(int key)877     private static Object removeListener(int key) {
878         if (key == INVALID_KEY) return null;
879         synchronized (sListenerMapLock) {
880             Object listener = sListenerMap.get(key);
881             sListenerMap.remove(key);
882             return listener;
883         }
884     }
885 
removeListener(Object listener)886     private static int removeListener(Object listener) {
887         int key = getListenerKey(listener);
888         if (key == INVALID_KEY) return key;
889         synchronized (sListenerMapLock) {
890             sListenerMap.remove(key);
891             return key;
892         }
893     }
894 
895     /** @hide */
896     public static class OperationResult implements Parcelable {
897         public int reason;
898         public String description;
899 
OperationResult(int reason, String description)900         public OperationResult(int reason, String description) {
901             this.reason = reason;
902             this.description = description;
903         }
904 
905         /** Implement the Parcelable interface {@hide} */
describeContents()906         public int describeContents() {
907             return 0;
908         }
909 
910         /** Implement the Parcelable interface {@hide} */
writeToParcel(Parcel dest, int flags)911         public void writeToParcel(Parcel dest, int flags) {
912             dest.writeInt(reason);
913             dest.writeString(description);
914         }
915 
916         /** Implement the Parcelable interface {@hide} */
917         public static final Creator<OperationResult> CREATOR =
918                 new Creator<OperationResult>() {
919                     public OperationResult createFromParcel(Parcel in) {
920                         int reason = in.readInt();
921                         String description = in.readString();
922                         return new OperationResult(reason, description);
923                     }
924 
925                     public OperationResult[] newArray(int size) {
926                         return new OperationResult[size];
927                     }
928                 };
929     }
930 
931     private static class ServiceHandler extends Handler {
ServiceHandler(Looper looper)932         ServiceHandler(Looper looper) {
933             super(looper);
934         }
935         @Override
handleMessage(Message msg)936         public void handleMessage(Message msg) {
937             switch (msg.what) {
938                 case AsyncChannel.CMD_CHANNEL_HALF_CONNECTED:
939                     if (msg.arg1 == AsyncChannel.STATUS_SUCCESSFUL) {
940                         sAsyncChannel.sendMessage(AsyncChannel.CMD_CHANNEL_FULL_CONNECTION);
941                     } else {
942                         Log.e(TAG, "Failed to set up channel connection");
943                         // This will cause all further async API calls on the WifiManager
944                         // to fail and throw an exception
945                         sAsyncChannel = null;
946                     }
947                     sConnected.countDown();
948                     return;
949                 case AsyncChannel.CMD_CHANNEL_FULLY_CONNECTED:
950                     return;
951                 case AsyncChannel.CMD_CHANNEL_DISCONNECTED:
952                     Log.e(TAG, "Channel connection lost");
953                     // This will cause all further async API calls on the WifiManager
954                     // to fail and throw an exception
955                     sAsyncChannel = null;
956                     getLooper().quit();
957                     return;
958             }
959 
960             Object listener = getListener(msg.arg2);
961 
962             if (listener == null) {
963                 if (DBG) Log.d(TAG, "invalid listener key = " + msg.arg2);
964                 return;
965             } else {
966                 if (DBG) Log.d(TAG, "listener key = " + msg.arg2);
967             }
968 
969             switch (msg.what) {
970                     /* ActionListeners grouped together */
971                 case CMD_OP_SUCCEEDED :
972                     ((ActionListener) listener).onSuccess();
973                     break;
974                 case CMD_OP_FAILED : {
975                         OperationResult result = (OperationResult)msg.obj;
976                         ((ActionListener) listener).onFailure(result.reason, result.description);
977                         removeListener(msg.arg2);
978                     }
979                     break;
980                 case CMD_SCAN_RESULT :
981                     ((ScanListener) listener).onResults(
982                             ((ParcelableScanData) msg.obj).getResults());
983                     return;
984                 case CMD_FULL_SCAN_RESULT :
985                     ScanResult result = (ScanResult) msg.obj;
986                     ((ScanListener) listener).onFullResult(result);
987                     return;
988                 case CMD_PERIOD_CHANGED:
989                     ((ScanListener) listener).onPeriodChanged(msg.arg1);
990                     return;
991                 case CMD_AP_FOUND:
992                     ((BssidListener) listener).onFound(
993                             ((ParcelableScanResults) msg.obj).getResults());
994                     return;
995                 case CMD_AP_LOST:
996                     ((BssidListener) listener).onLost(
997                             ((ParcelableScanResults) msg.obj).getResults());
998                     return;
999                 case CMD_WIFI_CHANGE_DETECTED:
1000                     ((WifiChangeListener) listener).onChanging(
1001                             ((ParcelableScanResults) msg.obj).getResults());
1002                    return;
1003                 case CMD_WIFI_CHANGES_STABILIZED:
1004                     ((WifiChangeListener) listener).onQuiescence(
1005                             ((ParcelableScanResults) msg.obj).getResults());
1006                     return;
1007                 case CMD_SINGLE_SCAN_COMPLETED:
1008                     if (DBG) Log.d(TAG, "removing listener for single scan");
1009                     removeListener(msg.arg2);
1010                     break;
1011                 default:
1012                     if (DBG) Log.d(TAG, "Ignoring message " + msg.what);
1013                     return;
1014             }
1015         }
1016     }
1017 }
1018