WifiScanner.java revision 011e1b35a64180d6f0234af8a3c2b70777eb9f39
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.content.Context;
20import android.os.Handler;
21import android.os.HandlerThread;
22import android.os.Looper;
23import android.os.Message;
24import android.os.Messenger;
25import android.os.Parcel;
26import android.os.Parcelable;
27import android.os.RemoteException;
28import android.util.Log;
29import android.util.SparseArray;
30
31import com.android.internal.util.AsyncChannel;
32import com.android.internal.util.Protocol;
33
34import java.util.ArrayList;
35import java.util.List;
36import java.util.concurrent.CountDownLatch;
37
38
39/**
40 * This class provides a way to scan the Wifi universe around the device
41 * Get an instance of this class by calling
42 * {@link android.content.Context#getSystemService(String) Context.getSystemService(Context
43 * .WIFI_SCANNING_SERVICE)}.
44 * @hide
45 */
46public class WifiScanner {
47
48    public static final int WIFI_BAND_UNSPECIFIED = 0;      /* not specified */
49    public static final int WIFI_BAND_24_GHZ = 1;           /* 2.4 GHz band */
50    public static final int WIFI_BAND_5_GHZ = 2;            /* 5 GHz band without DFS channels */
51    public static final int WIFI_BAND_5_GHZ_DFS_ONLY  = 4;  /* 5 GHz band with DFS channels */
52    public static final int WIFI_BAND_5_GHZ_WITH_DFS  = 6;  /* 5 GHz band with DFS channels */
53    public static final int WIFI_BAND_BOTH = 3;             /* both bands without DFS channels */
54    public static final int WIFI_BAND_BOTH_WITH_DFS = 7;    /* both bands with DFS channels */
55
56    public static final int MIN_SCAN_PERIOD_MS = 300;       /* minimum supported period */
57    public static final int MAX_SCAN_PERIOD_MS = 1024000;   /* maximum supported period */
58
59    public static final int REASON_SUCCEEDED = 0;
60    public static final int REASON_UNSPECIFIED = -1;
61    public static final int REASON_INVALID_LISTENER = -2;
62    public static final int REASON_INVALID_REQUEST = -3;
63    public static final int REASON_CONFLICTING_REQUEST = -4;
64
65    public static interface ActionListener {
66        public void onSuccess(Object result);
67        public void onFailure(int reason, Object exception);
68    }
69
70    /**
71     * gives you all the possible channels; channel is specified as an
72     * integer with frequency in MHz i.e. channel 1 is 2412
73     */
74    public List<Integer> getAvailableChannels(int band) {
75        return null;
76    }
77
78    /**
79     * provides channel specification to the APIs
80     */
81    public static class ChannelSpec {
82        public int frequency;
83        public boolean passive;                                    /* ignored on DFS channels */
84        public int dwellTimeMS;                                    /* not supported for now */
85
86        public ChannelSpec(int frequency) {
87            this.frequency = frequency;
88            passive = false;
89            dwellTimeMS = 0;
90        }
91    }
92
93    public static final int REPORT_EVENT_AFTER_BUFFER_FULL = 0;
94    public static final int REPORT_EVENT_AFTER_EACH_SCAN = 1;
95    public static final int REPORT_EVENT_FULL_SCAN_RESULT = 2;
96
97    /**
98     * scan configuration parameters
99     */
100    public static class ScanSettings implements Parcelable {
101
102        public int band;                                           /* ignore channels if specified */
103        public ChannelSpec[] channels;                             /* list of channels to scan */
104        public int periodInMs;                                     /* period of scan */
105        public int reportEvents;                                   /* a valid REPORT_EVENT value */
106
107        /** Implement the Parcelable interface {@hide} */
108        public int describeContents() {
109            return 0;
110        }
111
112        /** Implement the Parcelable interface {@hide} */
113        public void writeToParcel(Parcel dest, int flags) {
114            dest.writeInt(band);
115            dest.writeInt(periodInMs);
116            dest.writeInt(channels.length);
117
118            for (int i = 0; i < channels.length; i++) {
119                dest.writeInt(channels[i].frequency);
120                dest.writeInt(channels[i].dwellTimeMS);
121                dest.writeInt(channels[i].passive ? 1 : 0);
122            }
123        }
124
125        /** Implement the Parcelable interface {@hide} */
126        public static final Creator<ScanSettings> CREATOR =
127                new Creator<ScanSettings>() {
128                    public ScanSettings createFromParcel(Parcel in) {
129
130                        ScanSettings settings = new ScanSettings();
131                        settings.band = in.readInt();
132                        settings.periodInMs = in.readInt();
133                        int num_channels = in.readInt();
134                        settings.channels = new ChannelSpec[num_channels];
135                        for (int i = 0; i < num_channels; i++) {
136                            int frequency = in.readInt();
137
138                            ChannelSpec spec = new ChannelSpec(frequency);
139                            spec.dwellTimeMS = in.readInt();
140                            spec.passive = in.readInt() == 1;
141                            settings.channels[i] = spec;
142                        }
143
144                        return settings;
145                    }
146
147                    public ScanSettings[] newArray(int size) {
148                        return new ScanSettings[size];
149                    }
150                };
151
152    }
153
154    public static class InformationElement {
155        public int id;
156        public byte[] bytes;
157    }
158
159    public static class FullScanResult {
160        public ScanResult result;
161        public InformationElement informationElements[];
162    }
163
164    /** @hide */
165    public static class ParcelableScanResults implements Parcelable {
166        public ScanResult mResults[];
167
168        public ParcelableScanResults(ScanResult[] results) {
169            mResults = results;
170        }
171
172        public ScanResult[] getResults() {
173            return mResults;
174        }
175
176        /** Implement the Parcelable interface {@hide} */
177        public int describeContents() {
178            return 0;
179        }
180
181        /** Implement the Parcelable interface {@hide} */
182        public void writeToParcel(Parcel dest, int flags) {
183            dest.writeInt(mResults.length);
184            for (int i = 0; i < mResults.length; i++) {
185                ScanResult result = mResults[i];
186                result.writeToParcel(dest, flags);
187            }
188        }
189
190        /** Implement the Parcelable interface {@hide} */
191        public static final Creator<ParcelableScanResults> CREATOR =
192                new Creator<ParcelableScanResults>() {
193                    public ParcelableScanResults createFromParcel(Parcel in) {
194                        int n = in.readInt();
195                        ScanResult results[] = new ScanResult[n];
196                        for (int i = 0; i < n; i++) {
197                            results[i] = ScanResult.CREATOR.createFromParcel(in);
198                        }
199                        return new ParcelableScanResults(results);
200                    }
201
202                    public ParcelableScanResults[] newArray(int size) {
203                        return new ParcelableScanResults[size];
204                    }
205                };
206    }
207
208    /**
209     * Framework is co-ordinating scans across multiple apps; so it may not give exactly the
210     * same period requested. The period granted is stated on the onSuccess() event; and
211     * onPeriodChanged() will be called if/when it is changed because of multiple conflicting
212     * requests. This is similar to the way timers are handled.
213     */
214    public interface ScanListener extends ActionListener {
215        public void onPeriodChanged(int periodInMs);
216        public void onResults(ScanResult[] results);
217        public void onFullResult(FullScanResult fullScanResult);
218    }
219
220    public void scan(ScanSettings settings, ScanListener listener) {
221        validateChannel();
222        sAsyncChannel.sendMessage(CMD_SCAN, 0, putListener(listener), settings);
223    }
224    public void startBackgroundScan(ScanSettings settings, ScanListener listener) {
225        validateChannel();
226        sAsyncChannel.sendMessage(CMD_START_BACKGROUND_SCAN, 0, putListener(listener), settings);
227    }
228    public void stopBackgroundScan(boolean flush, ScanListener listener) {
229        validateChannel();
230        sAsyncChannel.sendMessage(CMD_STOP_BACKGROUND_SCAN, 0, removeListener(listener));
231    }
232    public void retrieveScanResults(boolean flush, ScanListener listener) {
233        validateChannel();
234        sAsyncChannel.sendMessage(CMD_GET_SCAN_RESULTS, 0, getListenerKey(listener));
235    }
236
237    public static class HotspotInfo {
238        public String bssid;
239        public int low;                                            /* minimum RSSI */
240        public int high;                                           /* maximum RSSI */
241    }
242
243    public static class WifiChangeSettings {
244        public int rssiSampleSize;                                 /* sample size for RSSI averaging */
245        public int lostApSampleSize;                               /* samples to confirm AP's loss */
246        public int unchangedSampleSize;                            /* samples to confirm no change */
247        public int minApsBreachingThreshold;                       /* change threshold to trigger event */
248        public HotspotInfo[] hotspotInfos;
249    }
250
251    /* overrides the significant wifi change state machine configuration */
252    public void configureSignificantWifiChange(
253            int rssiSampleSize,                             /* sample size for RSSI averaging */
254            int lostApSampleSize,                           /* samples to confirm AP's loss */
255            int unchangedSampleSize,                        /* samples to confirm no change */
256            int minApsBreachingThreshold,                   /* change threshold to trigger event */
257            HotspotInfo[] hotspotInfos                      /* signal thresholds to crosss */
258            )
259    {
260        validateChannel();
261        WifiChangeSettings settings = new WifiChangeSettings();
262        settings.rssiSampleSize = rssiSampleSize;
263        settings.lostApSampleSize = lostApSampleSize;
264        settings.unchangedSampleSize = unchangedSampleSize;
265        settings.minApsBreachingThreshold = minApsBreachingThreshold;
266        settings.hotspotInfos = hotspotInfos;
267
268        sAsyncChannel.sendMessage(CMD_CONFIGURE_WIFI_CHANGE, 0, 0, settings);
269    }
270
271    public interface SignificantWifiChangeListener extends ActionListener {
272        public void onChanging(ScanResult[] results);           /* changes are found */
273        public void onQuiescence(ScanResult[] results);         /* changes settled down */
274    }
275
276    public void trackSignificantWifiChange(SignificantWifiChangeListener listener) {
277        validateChannel();
278        sAsyncChannel.sendMessage(CMD_START_TRACKING_CHANGE, 0, putListener(listener));
279    }
280    public void untrackSignificantWifiChange(SignificantWifiChangeListener listener) {
281        validateChannel();
282        sAsyncChannel.sendMessage(CMD_STOP_TRACKING_CHANGE, 0, removeListener(listener));
283    }
284
285    public void configureSignificantWifiChange(WifiChangeSettings settings) {
286        validateChannel();
287        sAsyncChannel.sendMessage(CMD_CONFIGURE_WIFI_CHANGE, 0, 0, settings);
288    }
289
290    public static interface HotlistListener extends ActionListener {
291        public void onFound(ScanResult[] results);
292    }
293
294    /** @hide */
295    public static class HotlistSettings implements Parcelable {
296        public HotspotInfo[] hotspotInfos;
297        public int apLostThreshold;
298
299        /** Implement the Parcelable interface {@hide} */
300        public int describeContents() {
301            return 0;
302        }
303
304        /** Implement the Parcelable interface {@hide} */
305        public void writeToParcel(Parcel dest, int flags) {
306            dest.writeInt(apLostThreshold);
307            dest.writeInt(hotspotInfos.length);
308            for (int i = 0; i < hotspotInfos.length; i++) {
309                HotspotInfo info = hotspotInfos[i];
310                dest.writeString(info.bssid);
311                dest.writeInt(info.low);
312                dest.writeInt(info.high);
313            }
314        }
315
316        /** Implement the Parcelable interface {@hide} */
317        public static final Creator<HotlistSettings> CREATOR =
318                new Creator<HotlistSettings>() {
319                    public HotlistSettings createFromParcel(Parcel in) {
320                        HotlistSettings settings = new HotlistSettings();
321                        settings.apLostThreshold = in.readInt();
322                        int n = in.readInt();
323                        settings.hotspotInfos = new HotspotInfo[n];
324                        for (int i = 0; i < n; i++) {
325                            HotspotInfo info = new HotspotInfo();
326                            info.bssid = in.readString();
327                            info.low = in.readInt();
328                            info.high = in.readInt();
329                            settings.hotspotInfos[i] = info;
330                        }
331                        return settings;
332                    }
333
334                    public HotlistSettings[] newArray(int size) {
335                        return new HotlistSettings[size];
336                    }
337                };
338    }
339
340    public void setHotlist(HotspotInfo[] hotspots,
341            int apLostThreshold, HotlistListener listener) {
342        validateChannel();
343        HotlistSettings settings = new HotlistSettings();
344        settings.hotspotInfos = hotspots;
345        sAsyncChannel.sendMessage(CMD_SET_HOTLIST, 0, putListener(listener), settings);
346    }
347
348    public void resetHotlist(HotlistListener listener) {
349        validateChannel();
350        sAsyncChannel.sendMessage(CMD_RESET_HOTLIST, 0, removeListener(listener));
351    }
352
353
354    /* private members and methods */
355
356    private static final String TAG = "WifiScanner";
357    private static final boolean DBG = true;
358
359    /* commands for Wifi Service */
360    private static final int BASE = Protocol.BASE_WIFI_SCANNER;
361
362    /** @hide */
363    public static final int CMD_SCAN                        = BASE + 0;
364    /** @hide */
365    public static final int CMD_START_BACKGROUND_SCAN       = BASE + 2;
366    /** @hide */
367    public static final int CMD_STOP_BACKGROUND_SCAN        = BASE + 3;
368    /** @hide */
369    public static final int CMD_GET_SCAN_RESULTS            = BASE + 4;
370    /** @hide */
371    public static final int CMD_SCAN_RESULT                 = BASE + 5;
372    /** @hide */
373    public static final int CMD_SET_HOTLIST                 = BASE + 6;
374    /** @hide */
375    public static final int CMD_RESET_HOTLIST               = BASE + 7;
376    /** @hide */
377    public static final int CMD_AP_FOUND                    = BASE + 9;
378    /** @hide */
379    public static final int CMD_AP_LOST                     = BASE + 10;
380    /** @hide */
381    public static final int CMD_START_TRACKING_CHANGE       = BASE + 11;
382    /** @hide */
383    public static final int CMD_STOP_TRACKING_CHANGE        = BASE + 12;
384    /** @hide */
385    public static final int CMD_CONFIGURE_WIFI_CHANGE       = BASE + 13;
386    /** @hide */
387    public static final int CMD_WIFI_CHANGE_DETECTED        = BASE + 15;
388    /** @hide */
389    public static final int CMD_WIFI_CHANGES_STABILIZED     = BASE + 16;
390    /** @hide */
391    public static final int CMD_OP_SUCCEEDED                = BASE + 17;
392    /** @hide */
393    public static final int CMD_OP_FAILED                   = BASE + 18;
394    /** @hide */
395    public static final int CMD_PERIOD_CHANGED              = BASE + 19;
396    /** @hide */
397    public static final int CMD_FULL_SCAN_RESULT            = BASE + 20;
398
399    private Context mContext;
400    private IWifiScanner mService;
401
402    private static final int INVALID_KEY = 0;
403    private static int sListenerKey = 1;
404
405    private static final SparseArray sListenerMap = new SparseArray();
406    private static final Object sListenerMapLock = new Object();
407
408    private static AsyncChannel sAsyncChannel;
409    private static CountDownLatch sConnected;
410
411    private static final Object sThreadRefLock = new Object();
412    private static int sThreadRefCount;
413    private static HandlerThread sHandlerThread;
414
415    /**
416     * Create a new WifiScanner instance.
417     * Applications will almost always want to use
418     * {@link android.content.Context#getSystemService Context.getSystemService()} to retrieve
419     * the standard {@link android.content.Context#WIFI_SERVICE Context.WIFI_SERVICE}.
420     * @param context the application context
421     * @param service the Binder interface
422     * @hide
423     */
424    public WifiScanner(Context context, IWifiScanner service) {
425        mContext = context;
426        mService = service;
427        init();
428    }
429
430    private void init() {
431        synchronized (sThreadRefLock) {
432            if (++sThreadRefCount == 1) {
433                Messenger messenger = null;
434                try {
435                    messenger = mService.getMessenger();
436                } catch (RemoteException e) {
437                    /* do nothing */
438                } catch (SecurityException e) {
439                    /* do nothing */
440                }
441
442                if (messenger == null) {
443                    sAsyncChannel = null;
444                    return;
445                }
446
447                sHandlerThread = new HandlerThread("WifiScanner");
448                sAsyncChannel = new AsyncChannel();
449                sConnected = new CountDownLatch(1);
450
451                sHandlerThread.start();
452                Handler handler = new ServiceHandler(sHandlerThread.getLooper());
453                sAsyncChannel.connect(mContext, handler, messenger);
454                try {
455                    sConnected.await();
456                } catch (InterruptedException e) {
457                    Log.e(TAG, "interrupted wait at init");
458                }
459            }
460        }
461    }
462
463    private void validateChannel() {
464        if (sAsyncChannel == null) throw new IllegalStateException(
465                "No permission to access and change wifi or a bad initialization");
466    }
467
468    private static int putListener(Object listener) {
469        if (listener == null) return INVALID_KEY;
470        int key;
471        synchronized (sListenerMapLock) {
472            do {
473                key = sListenerKey++;
474            } while (key == INVALID_KEY);
475            sListenerMap.put(key, listener);
476        }
477        return key;
478    }
479
480    private static Object getListener(int key) {
481        if (key == INVALID_KEY) return null;
482        synchronized (sListenerMapLock) {
483            Object listener = sListenerMap.get(key);
484            return listener;
485        }
486    }
487
488    private static int getListenerKey(Object listener) {
489        if (listener == null) return INVALID_KEY;
490        synchronized (sListenerMapLock) {
491            int index = sListenerMap.indexOfValue(listener);
492            if (index == -1) {
493                return INVALID_KEY;
494            } else {
495                return sListenerMap.keyAt(index);
496            }
497        }
498    }
499
500    private static Object removeListener(int key) {
501        if (key == INVALID_KEY) return null;
502        synchronized (sListenerMapLock) {
503            Object listener = sListenerMap.get(key);
504            sListenerMap.remove(key);
505            return listener;
506        }
507    }
508
509    private static int removeListener(Object listener) {
510        int key = getListenerKey(listener);
511        if (key == INVALID_KEY) return key;
512        synchronized (sListenerMapLock) {
513            sListenerMap.remove(key);
514            return key;
515        }
516    }
517
518    private static class ServiceHandler extends Handler {
519        ServiceHandler(Looper looper) {
520            super(looper);
521        }
522        @Override
523        public void handleMessage(Message msg) {
524            switch (msg.what) {
525                case AsyncChannel.CMD_CHANNEL_HALF_CONNECTED:
526                    if (msg.arg1 == AsyncChannel.STATUS_SUCCESSFUL) {
527                        sAsyncChannel.sendMessage(AsyncChannel.CMD_CHANNEL_FULL_CONNECTION);
528                    } else {
529                        Log.e(TAG, "Failed to set up channel connection");
530                        // This will cause all further async API calls on the WifiManager
531                        // to fail and throw an exception
532                        sAsyncChannel = null;
533                    }
534                    sConnected.countDown();
535                    return;
536                case AsyncChannel.CMD_CHANNEL_FULLY_CONNECTED:
537                    return;
538                case AsyncChannel.CMD_CHANNEL_DISCONNECTED:
539                    Log.e(TAG, "Channel connection lost");
540                    // This will cause all further async API calls on the WifiManager
541                    // to fail and throw an exception
542                    sAsyncChannel = null;
543                    getLooper().quit();
544                    return;
545            }
546
547            Object listener = getListener(msg.arg2);
548            if (DBG) Log.d(TAG, "listener key = " + msg.arg2);
549
550            switch (msg.what) {
551                    /* ActionListeners grouped together */
552                case CMD_OP_SUCCEEDED :
553                    ((ActionListener) listener).onSuccess(msg.obj);
554                    break;
555                case CMD_OP_FAILED :
556                    ((ActionListener) listener).onFailure(msg.arg1, msg.obj);
557                    break;
558                case CMD_SCAN_RESULT :
559                    ((ScanListener) listener).onResults(
560                            ((ParcelableScanResults) msg.obj).getResults());
561                    return;
562                case CMD_FULL_SCAN_RESULT :
563                    FullScanResult result = (FullScanResult) msg.obj;
564                    ((ScanListener) listener).onFullResult(result);
565                    return;
566                case CMD_AP_FOUND:
567                    ((HotlistListener) listener).onFound(
568                            ((ParcelableScanResults) msg.obj).getResults());
569                    return;
570                case CMD_WIFI_CHANGE_DETECTED:
571                    ((SignificantWifiChangeListener) listener).onChanging(
572                            ((ParcelableScanResults) msg.obj).getResults());
573                   return;
574                case CMD_WIFI_CHANGES_STABILIZED:
575                    ((SignificantWifiChangeListener) listener).onQuiescence(
576                            ((ParcelableScanResults) msg.obj).getResults());
577                    return;
578                default:
579                    if (DBG) Log.d(TAG, "Ignoring message " + msg.what);
580                    return;
581            }
582        }
583    }
584}
585