2 * Copyright (C) 2008 The Android Open Source Project
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
8 * http://www.apache.org/licenses/LICENSE-2.0
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.
17 package android.net.wifi;
19 import android.annotation.SystemApi;
20 import android.content.Context;
21 import android.os.Bundle;
22 import android.os.Handler;
23 import android.os.Looper;
24 import android.os.Message;
25 import android.os.Messenger;
26 import android.os.Parcel;
27 import android.os.Parcelable;
28 import android.os.RemoteException;
29 import android.os.WorkSource;
30 import android.util.Log;
31 import android.util.SparseArray;
33 import com.android.internal.util.AsyncChannel;
34 import com.android.internal.util.Preconditions;
35 import com.android.internal.util.Protocol;
37 import java.util.List;
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)}.
48 public class WifiScanner {
50 /** no band specified; use channel list instead */
51 public static final int WIFI_BAND_UNSPECIFIED = 0; /* not specified */
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 */
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 */
72 public static final int REASON_SUCCEEDED = 0;
74 public static final int REASON_UNSPECIFIED = -1;
75 /** Invalid listener */
76 public static final int REASON_INVALID_LISTENER = -2;
77 /** Invalid request */
78 public static final int REASON_INVALID_REQUEST = -3;
79 /** Invalid request */
80 public static final int REASON_NOT_AUTHORIZED = -4;
81 /** An outstanding request with the same listener hasn't finished yet. */
82 public static final int REASON_DUPLICATE_REQEUST = -5;
85 public static final String GET_AVAILABLE_CHANNELS_EXTRA = "Channels";
88 * Generic action callback invocation interface
92 public static interface ActionListener {
93 public void onSuccess();
94 public void onFailure(int reason, String description);
98 * gives you all the possible channels; channel is specified as an
99 * integer with frequency in MHz i.e. channel 1 is 2412
102 public List<Integer> getAvailableChannels(int band) {
104 Bundle bundle = mService.getAvailableChannels(band);
105 return bundle.getIntegerArrayList(GET_AVAILABLE_CHANNELS_EXTRA);
106 } catch (RemoteException e) {
112 * provides channel specification for scanning
114 public static class ChannelSpec {
116 * channel frequency in MHz; for example channel 1 is specified as 2412
118 public int frequency;
120 * if true, scan this channel in passive fashion.
121 * This flag is ignored on DFS channel specification.
124 public boolean passive; /* ignored on DFS channels */
126 * how long to dwell on this channel
129 public int dwellTimeMS; /* not supported for now */
132 * default constructor for channel spec
134 public ChannelSpec(int frequency) {
135 this.frequency = frequency;
142 * reports {@link ScanListener#onResults} when underlying buffers are full
143 * this is simply the lack of the {@link #REPORT_EVENT_AFTER_EACH_SCAN} flag
144 * @deprecated It is not supported anymore.
147 public static final int REPORT_EVENT_AFTER_BUFFER_FULL = 0;
149 * reports {@link ScanListener#onResults} after each scan
151 public static final int REPORT_EVENT_AFTER_EACH_SCAN = (1 << 0);
153 * reports {@link ScanListener#onFullResult} whenever each beacon is discovered
155 public static final int REPORT_EVENT_FULL_SCAN_RESULT = (1 << 1);
157 * Do not place scans in the chip's scan history buffer
159 public static final int REPORT_EVENT_NO_BATCH = (1 << 2);
163 public static final String SCAN_PARAMS_SCAN_SETTINGS_KEY = "ScanSettings";
165 public static final String SCAN_PARAMS_WORK_SOURCE_KEY = "WorkSource";
167 * scan configuration parameters to be sent to {@link #startBackgroundScan}
169 public static class ScanSettings implements Parcelable {
171 /** one of the WIFI_BAND values */
173 /** list of channels; used when band is set to WIFI_BAND_UNSPECIFIED */
174 public ChannelSpec[] channels;
176 * list of networkId's of hidden networks to scan for.
177 * These Id's should correspond to the wpa_supplicant's networkId's and will be used
178 * in connectivity scans using wpa_supplicant.
181 public int[] hiddenNetworkIds;
182 /** period of background scan; in millisecond, 0 => single shot scan */
183 public int periodInMs;
184 /** must have a valid REPORT_EVENT value */
185 public int reportEvents;
186 /** defines number of bssids to cache from each scan */
187 public int numBssidsPerScan;
189 * defines number of scans to cache; use it with REPORT_EVENT_AFTER_BUFFER_FULL
190 * to wake up at fixed interval
192 public int maxScansToCache;
194 * if maxPeriodInMs is non zero or different than period, then this bucket is
195 * a truncated binary exponential backoff bucket and the scan period will grow
196 * exponentially as per formula: actual_period(N) = period * (2 ^ (N/stepCount))
199 public int maxPeriodInMs;
201 * for truncated binary exponential back off bucket, number of scans to perform
204 public int stepCount;
206 * Flag to indicate if the scan settings are targeted for PNO scan.
209 public boolean isPnoScan;
211 /** Implement the Parcelable interface {@hide} */
212 public int describeContents() {
216 /** Implement the Parcelable interface {@hide} */
217 public void writeToParcel(Parcel dest, int flags) {
219 dest.writeInt(periodInMs);
220 dest.writeInt(reportEvents);
221 dest.writeInt(numBssidsPerScan);
222 dest.writeInt(maxScansToCache);
223 dest.writeInt(maxPeriodInMs);
224 dest.writeInt(stepCount);
225 dest.writeInt(isPnoScan ? 1 : 0);
226 if (channels != null) {
227 dest.writeInt(channels.length);
228 for (int i = 0; i < channels.length; i++) {
229 dest.writeInt(channels[i].frequency);
230 dest.writeInt(channels[i].dwellTimeMS);
231 dest.writeInt(channels[i].passive ? 1 : 0);
236 dest.writeIntArray(hiddenNetworkIds);
239 /** Implement the Parcelable interface {@hide} */
240 public static final Creator<ScanSettings> CREATOR =
241 new Creator<ScanSettings>() {
242 public ScanSettings createFromParcel(Parcel in) {
243 ScanSettings settings = new ScanSettings();
244 settings.band = in.readInt();
245 settings.periodInMs = in.readInt();
246 settings.reportEvents = in.readInt();
247 settings.numBssidsPerScan = in.readInt();
248 settings.maxScansToCache = in.readInt();
249 settings.maxPeriodInMs = in.readInt();
250 settings.stepCount = in.readInt();
251 settings.isPnoScan = in.readInt() == 1;
252 int num_channels = in.readInt();
253 settings.channels = new ChannelSpec[num_channels];
254 for (int i = 0; i < num_channels; i++) {
255 int frequency = in.readInt();
256 ChannelSpec spec = new ChannelSpec(frequency);
257 spec.dwellTimeMS = in.readInt();
258 spec.passive = in.readInt() == 1;
259 settings.channels[i] = spec;
261 settings.hiddenNetworkIds = in.createIntArray();
265 public ScanSettings[] newArray(int size) {
266 return new ScanSettings[size];
273 * all the information garnered from a single scan
275 public static class ScanData implements Parcelable {
276 /** scan identifier */
278 /** additional information about scan
279 * 0 => no special issues encountered in the scan
280 * non-zero => scan was truncated, so results may not be complete
284 * Indicates the buckets that were scanned to generate these results.
285 * This is not relevant to WifiScanner API users and is used internally.
288 private int mBucketsScanned;
289 /** all scan results discovered in this scan, sorted by timestamp in ascending order */
290 private ScanResult mResults[];
294 public ScanData(int id, int flags, ScanResult[] results) {
301 public ScanData(int id, int flags, int bucketsScanned, ScanResult[] results) {
304 mBucketsScanned = bucketsScanned;
308 public ScanData(ScanData s) {
311 mBucketsScanned = s.mBucketsScanned;
312 mResults = new ScanResult[s.mResults.length];
313 for (int i = 0; i < s.mResults.length; i++) {
314 ScanResult result = s.mResults[i];
315 ScanResult newResult = new ScanResult(result);
316 mResults[i] = newResult;
324 public int getFlags() {
329 public int getBucketsScanned() {
330 return mBucketsScanned;
333 public ScanResult[] getResults() {
337 /** Implement the Parcelable interface {@hide} */
338 public int describeContents() {
342 /** Implement the Parcelable interface {@hide} */
343 public void writeToParcel(Parcel dest, int flags) {
344 if (mResults != null) {
346 dest.writeInt(mFlags);
347 dest.writeInt(mBucketsScanned);
348 dest.writeInt(mResults.length);
349 for (int i = 0; i < mResults.length; i++) {
350 ScanResult result = mResults[i];
351 result.writeToParcel(dest, flags);
358 /** Implement the Parcelable interface {@hide} */
359 public static final Creator<ScanData> CREATOR =
360 new Creator<ScanData>() {
361 public ScanData createFromParcel(Parcel in) {
362 int id = in.readInt();
363 int flags = in.readInt();
364 int bucketsScanned = in.readInt();
365 int n = in.readInt();
366 ScanResult results[] = new ScanResult[n];
367 for (int i = 0; i < n; i++) {
368 results[i] = ScanResult.CREATOR.createFromParcel(in);
370 return new ScanData(id, flags, bucketsScanned, results);
373 public ScanData[] newArray(int size) {
374 return new ScanData[size];
379 public static class ParcelableScanData implements Parcelable {
381 public ScanData mResults[];
383 public ParcelableScanData(ScanData[] results) {
387 public ScanData[] getResults() {
391 /** Implement the Parcelable interface {@hide} */
392 public int describeContents() {
396 /** Implement the Parcelable interface {@hide} */
397 public void writeToParcel(Parcel dest, int flags) {
398 if (mResults != null) {
399 dest.writeInt(mResults.length);
400 for (int i = 0; i < mResults.length; i++) {
401 ScanData result = mResults[i];
402 result.writeToParcel(dest, flags);
409 /** Implement the Parcelable interface {@hide} */
410 public static final Creator<ParcelableScanData> CREATOR =
411 new Creator<ParcelableScanData>() {
412 public ParcelableScanData createFromParcel(Parcel in) {
413 int n = in.readInt();
414 ScanData results[] = new ScanData[n];
415 for (int i = 0; i < n; i++) {
416 results[i] = ScanData.CREATOR.createFromParcel(in);
418 return new ParcelableScanData(results);
421 public ParcelableScanData[] newArray(int size) {
422 return new ParcelableScanData[size];
427 public static class ParcelableScanResults implements Parcelable {
429 public ScanResult mResults[];
431 public ParcelableScanResults(ScanResult[] results) {
435 public ScanResult[] getResults() {
439 /** Implement the Parcelable interface {@hide} */
440 public int describeContents() {
444 /** Implement the Parcelable interface {@hide} */
445 public void writeToParcel(Parcel dest, int flags) {
446 if (mResults != null) {
447 dest.writeInt(mResults.length);
448 for (int i = 0; i < mResults.length; i++) {
449 ScanResult result = mResults[i];
450 result.writeToParcel(dest, flags);
457 /** Implement the Parcelable interface {@hide} */
458 public static final Creator<ParcelableScanResults> CREATOR =
459 new Creator<ParcelableScanResults>() {
460 public ParcelableScanResults createFromParcel(Parcel in) {
461 int n = in.readInt();
462 ScanResult results[] = new ScanResult[n];
463 for (int i = 0; i < n; i++) {
464 results[i] = ScanResult.CREATOR.createFromParcel(in);
466 return new ParcelableScanResults(results);
469 public ParcelableScanResults[] newArray(int size) {
470 return new ParcelableScanResults[size];
476 public static final String PNO_PARAMS_PNO_SETTINGS_KEY = "PnoSettings";
478 public static final String PNO_PARAMS_SCAN_SETTINGS_KEY = "ScanSettings";
480 * PNO scan configuration parameters to be sent to {@link #startPnoScan}.
481 * Note: This structure needs to be in sync with |wifi_epno_params| struct in gscan HAL API.
484 public static class PnoSettings implements Parcelable {
486 * Pno network to be added to the PNO scan filtering.
489 public static class PnoNetwork {
491 * Pno flags bitmask to be set in {@link #PnoNetwork.flags}
493 /** Whether directed scan needs to be performed (for hidden SSIDs) */
494 public static final byte FLAG_DIRECTED_SCAN = (1 << 0);
495 /** Whether PNO event shall be triggered if the network is found on A band */
496 public static final byte FLAG_A_BAND = (1 << 1);
497 /** Whether PNO event shall be triggered if the network is found on G band */
498 public static final byte FLAG_G_BAND = (1 << 2);
500 * Whether strict matching is required
501 * If required then the firmware must store the network's SSID and not just a hash
503 public static final byte FLAG_STRICT_MATCH = (1 << 3);
505 * If this SSID should be considered the same network as the currently connected
508 public static final byte FLAG_SAME_NETWORK = (1 << 4);
511 * Code for matching the beacon AUTH IE - additional codes. Bitmask to be set in
512 * {@link #PnoNetwork.authBitField}
515 public static final byte AUTH_CODE_OPEN = (1 << 0);
516 /** WPA_PSK or WPA2PSK */
517 public static final byte AUTH_CODE_PSK = (1 << 1);
519 public static final byte AUTH_CODE_EAPOL = (1 << 2);
521 /** SSID of the network */
523 /** Network ID in wpa_supplicant */
524 public int networkId;
525 /** Assigned priority for the network */
527 /** Bitmask of the FLAG_XXX */
529 /** Bitmask of the ATUH_XXX */
530 public byte authBitField;
533 * default constructor for PnoNetwork
535 public PnoNetwork(String ssid) {
542 /** Connected vs Disconnected PNO flag {@hide} */
543 public boolean isConnected;
544 /** Minimum 5GHz RSSI for a BSSID to be considered */
545 public int min5GHzRssi;
546 /** Minimum 2.4GHz RSSI for a BSSID to be considered */
547 public int min24GHzRssi;
548 /** Maximum score that a network can have before bonuses */
549 public int initialScoreMax;
551 * Only report when there is a network's score this much higher
552 * than the current connection.
554 public int currentConnectionBonus;
555 /** score bonus for all networks with the same network flag */
556 public int sameNetworkBonus;
557 /** score bonus for networks that are not open */
558 public int secureBonus;
559 /** 5GHz RSSI score bonus (applied to all 5GHz networks) */
560 public int band5GHzBonus;
561 /** Pno Network filter list */
562 public PnoNetwork[] networkList;
564 /** Implement the Parcelable interface {@hide} */
565 public int describeContents() {
569 /** Implement the Parcelable interface {@hide} */
570 public void writeToParcel(Parcel dest, int flags) {
571 dest.writeInt(isConnected ? 1 : 0);
572 dest.writeInt(min5GHzRssi);
573 dest.writeInt(min24GHzRssi);
574 dest.writeInt(initialScoreMax);
575 dest.writeInt(currentConnectionBonus);
576 dest.writeInt(sameNetworkBonus);
577 dest.writeInt(secureBonus);
578 dest.writeInt(band5GHzBonus);
579 if (networkList != null) {
580 dest.writeInt(networkList.length);
581 for (int i = 0; i < networkList.length; i++) {
582 dest.writeString(networkList[i].ssid);
583 dest.writeInt(networkList[i].networkId);
584 dest.writeInt(networkList[i].priority);
585 dest.writeByte(networkList[i].flags);
586 dest.writeByte(networkList[i].authBitField);
593 /** Implement the Parcelable interface {@hide} */
594 public static final Creator<PnoSettings> CREATOR =
595 new Creator<PnoSettings>() {
596 public PnoSettings createFromParcel(Parcel in) {
597 PnoSettings settings = new PnoSettings();
598 settings.isConnected = in.readInt() == 1;
599 settings.min5GHzRssi = in.readInt();
600 settings.min24GHzRssi = in.readInt();
601 settings.initialScoreMax = in.readInt();
602 settings.currentConnectionBonus = in.readInt();
603 settings.sameNetworkBonus = in.readInt();
604 settings.secureBonus = in.readInt();
605 settings.band5GHzBonus = in.readInt();
606 int numNetworks = in.readInt();
607 settings.networkList = new PnoNetwork[numNetworks];
608 for (int i = 0; i < numNetworks; i++) {
609 String ssid = in.readString();
610 PnoNetwork network = new PnoNetwork(ssid);
611 network.networkId = in.readInt();
612 network.priority = in.readInt();
613 network.flags = in.readByte();
614 network.authBitField = in.readByte();
615 settings.networkList[i] = network;
620 public PnoSettings[] newArray(int size) {
621 return new PnoSettings[size];
628 * interface to get scan events on; specify this on {@link #startBackgroundScan} or
631 public interface ScanListener extends ActionListener {
633 * Framework co-ordinates scans across multiple apps; so it may not give exactly the
634 * same period requested. If period of a scan is changed; it is reported by this event.
636 public void onPeriodChanged(int periodInMs);
638 * reports results retrieved from background scan and single shot scans
640 public void onResults(ScanData[] results);
642 * reports full scan result for each access point found in scan
644 public void onFullResult(ScanResult fullScanResult);
648 * interface to get PNO scan events on; specify this on {@link #startDisconnectedPnoScan} and
649 * {@link #startConnectedPnoScan}.
652 public interface PnoScanListener extends ScanListener {
654 * Invoked when one of the PNO networks are found in scan results.
656 void onPnoNetworkFound(ScanResult[] results);
659 /** start wifi scan in background
660 * @param settings specifies various parameters for the scan; for more information look at
661 * {@link ScanSettings}
662 * @param listener specifies the object to report events to. This object is also treated as a
663 * key for this scan, and must also be specified to cancel the scan. Multiple
664 * scans should also not share this object.
666 public void startBackgroundScan(ScanSettings settings, ScanListener listener) {
667 startBackgroundScan(settings, listener, null);
670 /** start wifi scan in background
671 * @param settings specifies various parameters for the scan; for more information look at
672 * {@link ScanSettings}
673 * @param workSource WorkSource to blame for power usage
674 * @param listener specifies the object to report events to. This object is also treated as a
675 * key for this scan, and must also be specified to cancel the scan. Multiple
676 * scans should also not share this object.
678 public void startBackgroundScan(ScanSettings settings, ScanListener listener,
679 WorkSource workSource) {
680 Preconditions.checkNotNull(listener, "listener cannot be null");
681 int key = addListener(listener);
682 if (key == INVALID_KEY) return;
684 Bundle scanParams = new Bundle();
685 scanParams.putParcelable(SCAN_PARAMS_SCAN_SETTINGS_KEY, settings);
686 scanParams.putParcelable(SCAN_PARAMS_WORK_SOURCE_KEY, workSource);
687 mAsyncChannel.sendMessage(CMD_START_BACKGROUND_SCAN, 0, key, scanParams);
691 * stop an ongoing wifi scan
692 * @param listener specifies which scan to cancel; must be same object as passed in {@link
693 * #startBackgroundScan}
695 public void stopBackgroundScan(ScanListener listener) {
696 Preconditions.checkNotNull(listener, "listener cannot be null");
697 int key = removeListener(listener);
698 if (key == INVALID_KEY) return;
700 mAsyncChannel.sendMessage(CMD_STOP_BACKGROUND_SCAN, 0, key);
703 * reports currently available scan results on appropriate listeners
704 * @return true if all scan results were reported correctly
706 public boolean getScanResults() {
708 Message reply = mAsyncChannel.sendMessageSynchronously(CMD_GET_SCAN_RESULTS, 0);
709 return reply.what == CMD_OP_SUCCEEDED;
713 * starts a single scan and reports results asynchronously
714 * @param settings specifies various parameters for the scan; for more information look at
715 * {@link ScanSettings}
716 * @param listener specifies the object to report events to. This object is also treated as a
717 * key for this scan, and must also be specified to cancel the scan. Multiple
718 * scans should also not share this object.
720 public void startScan(ScanSettings settings, ScanListener listener) {
721 startScan(settings, listener, null);
725 * starts a single scan and reports results asynchronously
726 * @param settings specifies various parameters for the scan; for more information look at
727 * {@link ScanSettings}
728 * @param workSource WorkSource to blame for power usage
729 * @param listener specifies the object to report events to. This object is also treated as a
730 * key for this scan, and must also be specified to cancel the scan. Multiple
731 * scans should also not share this object.
733 public void startScan(ScanSettings settings, ScanListener listener, WorkSource workSource) {
734 Preconditions.checkNotNull(listener, "listener cannot be null");
735 int key = addListener(listener);
736 if (key == INVALID_KEY) return;
738 Bundle scanParams = new Bundle();
739 scanParams.putParcelable(SCAN_PARAMS_SCAN_SETTINGS_KEY, settings);
740 scanParams.putParcelable(SCAN_PARAMS_WORK_SOURCE_KEY, workSource);
741 mAsyncChannel.sendMessage(CMD_START_SINGLE_SCAN, 0, key, scanParams);
745 * stops an ongoing single shot scan; only useful after {@link #startScan} if onResults()
746 * hasn't been called on the listener, ignored otherwise
749 public void stopScan(ScanListener listener) {
750 Preconditions.checkNotNull(listener, "listener cannot be null");
751 int key = removeListener(listener);
752 if (key == INVALID_KEY) return;
754 mAsyncChannel.sendMessage(CMD_STOP_SINGLE_SCAN, 0, key);
757 private void startPnoScan(ScanSettings scanSettings, PnoSettings pnoSettings, int key) {
758 // Bundle up both the settings and send it across.
759 Bundle pnoParams = new Bundle();
760 // Set the PNO scan flag.
761 scanSettings.isPnoScan = true;
762 pnoParams.putParcelable(PNO_PARAMS_SCAN_SETTINGS_KEY, scanSettings);
763 pnoParams.putParcelable(PNO_PARAMS_PNO_SETTINGS_KEY, pnoSettings);
764 mAsyncChannel.sendMessage(CMD_START_PNO_SCAN, 0, key, pnoParams);
767 * Start wifi connected PNO scan
768 * @param scanSettings specifies various parameters for the scan; for more information look at
769 * {@link ScanSettings}
770 * @param pnoSettings specifies various parameters for PNO; for more information look at
771 * {@link PnoSettings}
772 * @param listener specifies the object to report events to. This object is also treated as a
773 * key for this scan, and must also be specified to cancel the scan. Multiple
774 * scans should also not share this object.
777 public void startConnectedPnoScan(ScanSettings scanSettings, PnoSettings pnoSettings,
778 PnoScanListener listener) {
779 Preconditions.checkNotNull(listener, "listener cannot be null");
780 Preconditions.checkNotNull(pnoSettings, "pnoSettings cannot be null");
781 int key = addListener(listener);
782 if (key == INVALID_KEY) return;
784 pnoSettings.isConnected = true;
785 startPnoScan(scanSettings, pnoSettings, key);
788 * Start wifi disconnected PNO scan
789 * @param scanSettings specifies various parameters for the scan; for more information look at
790 * {@link ScanSettings}
791 * @param pnoSettings specifies various parameters for PNO; for more information look at
792 * {@link PnoSettings}
793 * @param listener specifies the object to report events to. This object is also treated as a
794 * key for this scan, and must also be specified to cancel the scan. Multiple
795 * scans should also not share this object.
798 public void startDisconnectedPnoScan(ScanSettings scanSettings, PnoSettings pnoSettings,
799 PnoScanListener listener) {
800 Preconditions.checkNotNull(listener, "listener cannot be null");
801 Preconditions.checkNotNull(pnoSettings, "pnoSettings cannot be null");
802 int key = addListener(listener);
803 if (key == INVALID_KEY) return;
805 pnoSettings.isConnected = false;
806 startPnoScan(scanSettings, pnoSettings, key);
809 * Stop an ongoing wifi PNO scan
810 * @param listener specifies which scan to cancel; must be same object as passed in {@link
812 * TODO(rpius): Check if we can remove pnoSettings param in stop.
815 public void stopPnoScan(ScanListener listener) {
816 Preconditions.checkNotNull(listener, "listener cannot be null");
817 int key = removeListener(listener);
818 if (key == INVALID_KEY) return;
820 mAsyncChannel.sendMessage(CMD_STOP_PNO_SCAN, 0, key);
823 /** specifies information about an access point of interest */
824 public static class BssidInfo {
825 /** bssid of the access point; in XX:XX:XX:XX:XX:XX format */
827 /** low signal strength threshold; more information at {@link ScanResult#level} */
828 public int low; /* minimum RSSI */
829 /** high signal threshold; more information at {@link ScanResult#level} */
830 public int high; /* maximum RSSI */
831 /** channel frequency (in KHz) where you may find this BSSID */
832 public int frequencyHint;
837 public static class WifiChangeSettings implements Parcelable {
838 public int rssiSampleSize; /* sample size for RSSI averaging */
839 public int lostApSampleSize; /* samples to confirm AP's loss */
840 public int unchangedSampleSize; /* samples to confirm no change */
841 public int minApsBreachingThreshold; /* change threshold to trigger event */
842 public int periodInMs; /* scan period in millisecond */
843 public BssidInfo[] bssidInfos;
845 /** Implement the Parcelable interface {@hide} */
846 public int describeContents() {
850 /** Implement the Parcelable interface {@hide} */
851 public void writeToParcel(Parcel dest, int flags) {
852 dest.writeInt(rssiSampleSize);
853 dest.writeInt(lostApSampleSize);
854 dest.writeInt(unchangedSampleSize);
855 dest.writeInt(minApsBreachingThreshold);
856 dest.writeInt(periodInMs);
857 if (bssidInfos != null) {
858 dest.writeInt(bssidInfos.length);
859 for (int i = 0; i < bssidInfos.length; i++) {
860 BssidInfo info = bssidInfos[i];
861 dest.writeString(info.bssid);
862 dest.writeInt(info.low);
863 dest.writeInt(info.high);
864 dest.writeInt(info.frequencyHint);
871 /** Implement the Parcelable interface {@hide} */
872 public static final Creator<WifiChangeSettings> CREATOR =
873 new Creator<WifiChangeSettings>() {
874 public WifiChangeSettings createFromParcel(Parcel in) {
875 WifiChangeSettings settings = new WifiChangeSettings();
876 settings.rssiSampleSize = in.readInt();
877 settings.lostApSampleSize = in.readInt();
878 settings.unchangedSampleSize = in.readInt();
879 settings.minApsBreachingThreshold = in.readInt();
880 settings.periodInMs = in.readInt();
881 int len = in.readInt();
882 settings.bssidInfos = new BssidInfo[len];
883 for (int i = 0; i < len; i++) {
884 BssidInfo info = new BssidInfo();
885 info.bssid = in.readString();
886 info.low = in.readInt();
887 info.high = in.readInt();
888 info.frequencyHint = in.readInt();
889 settings.bssidInfos[i] = info;
894 public WifiChangeSettings[] newArray(int size) {
895 return new WifiChangeSettings[size];
901 /** configure WifiChange detection
902 * @param rssiSampleSize number of samples used for RSSI averaging
903 * @param lostApSampleSize number of samples to confirm an access point's loss
904 * @param unchangedSampleSize number of samples to confirm there are no changes
905 * @param minApsBreachingThreshold minimum number of access points that need to be
906 * out of range to detect WifiChange
907 * @param periodInMs indicates period of scan to find changes
908 * @param bssidInfos access points to watch
910 public void configureWifiChange(
911 int rssiSampleSize, /* sample size for RSSI averaging */
912 int lostApSampleSize, /* samples to confirm AP's loss */
913 int unchangedSampleSize, /* samples to confirm no change */
914 int minApsBreachingThreshold, /* change threshold to trigger event */
915 int periodInMs, /* period of scan */
916 BssidInfo[] bssidInfos /* signal thresholds to crosss */
921 WifiChangeSettings settings = new WifiChangeSettings();
922 settings.rssiSampleSize = rssiSampleSize;
923 settings.lostApSampleSize = lostApSampleSize;
924 settings.unchangedSampleSize = unchangedSampleSize;
925 settings.minApsBreachingThreshold = minApsBreachingThreshold;
926 settings.periodInMs = periodInMs;
927 settings.bssidInfos = bssidInfos;
929 configureWifiChange(settings);
933 * interface to get wifi change events on; use this on {@link #startTrackingWifiChange}
935 public interface WifiChangeListener extends ActionListener {
936 /** indicates that changes were detected in wifi environment
937 * @param results indicate the access points that exhibited change
939 public void onChanging(ScanResult[] results); /* changes are found */
940 /** indicates that no wifi changes are being detected for a while
941 * @param results indicate the access points that are bing monitored for change
943 public void onQuiescence(ScanResult[] results); /* changes settled down */
947 * track changes in wifi environment
948 * @param listener object to report events on; this object must be unique and must also be
949 * provided on {@link #stopTrackingWifiChange}
951 public void startTrackingWifiChange(WifiChangeListener listener) {
952 Preconditions.checkNotNull(listener, "listener cannot be null");
953 int key = addListener(listener);
954 if (key == INVALID_KEY) return;
956 mAsyncChannel.sendMessage(CMD_START_TRACKING_CHANGE, 0, key);
960 * stop tracking changes in wifi environment
961 * @param listener object that was provided to report events on {@link
962 * #stopTrackingWifiChange}
964 public void stopTrackingWifiChange(WifiChangeListener listener) {
965 int key = removeListener(listener);
966 if (key == INVALID_KEY) return;
968 mAsyncChannel.sendMessage(CMD_STOP_TRACKING_CHANGE, 0, key);
973 public void configureWifiChange(WifiChangeSettings settings) {
975 mAsyncChannel.sendMessage(CMD_CONFIGURE_WIFI_CHANGE, 0, 0, settings);
978 /** interface to receive hotlist events on; use this on {@link #setHotlist} */
979 public static interface BssidListener extends ActionListener {
980 /** indicates that access points were found by on going scans
981 * @param results list of scan results, one for each access point visible currently
983 public void onFound(ScanResult[] results);
984 /** indicates that access points were missed by on going scans
985 * @param results list of scan results, for each access point that is not visible anymore
987 public void onLost(ScanResult[] results);
992 public static class HotlistSettings implements Parcelable {
993 public BssidInfo[] bssidInfos;
994 public int apLostThreshold;
996 /** Implement the Parcelable interface {@hide} */
997 public int describeContents() {
1001 /** Implement the Parcelable interface {@hide} */
1002 public void writeToParcel(Parcel dest, int flags) {
1003 dest.writeInt(apLostThreshold);
1005 if (bssidInfos != null) {
1006 dest.writeInt(bssidInfos.length);
1007 for (int i = 0; i < bssidInfos.length; i++) {
1008 BssidInfo info = bssidInfos[i];
1009 dest.writeString(info.bssid);
1010 dest.writeInt(info.low);
1011 dest.writeInt(info.high);
1012 dest.writeInt(info.frequencyHint);
1019 /** Implement the Parcelable interface {@hide} */
1020 public static final Creator<HotlistSettings> CREATOR =
1021 new Creator<HotlistSettings>() {
1022 public HotlistSettings createFromParcel(Parcel in) {
1023 HotlistSettings settings = new HotlistSettings();
1024 settings.apLostThreshold = in.readInt();
1025 int n = in.readInt();
1026 settings.bssidInfos = new BssidInfo[n];
1027 for (int i = 0; i < n; i++) {
1028 BssidInfo info = new BssidInfo();
1029 info.bssid = in.readString();
1030 info.low = in.readInt();
1031 info.high = in.readInt();
1032 info.frequencyHint = in.readInt();
1033 settings.bssidInfos[i] = info;
1038 public HotlistSettings[] newArray(int size) {
1039 return new HotlistSettings[size];
1045 * set interesting access points to find
1046 * @param bssidInfos access points of interest
1047 * @param apLostThreshold number of scans needed to indicate that AP is lost
1048 * @param listener object provided to report events on; this object must be unique and must
1049 * also be provided on {@link #stopTrackingBssids}
1051 public void startTrackingBssids(BssidInfo[] bssidInfos,
1052 int apLostThreshold, BssidListener listener) {
1053 Preconditions.checkNotNull(listener, "listener cannot be null");
1054 int key = addListener(listener);
1055 if (key == INVALID_KEY) return;
1057 HotlistSettings settings = new HotlistSettings();
1058 settings.bssidInfos = bssidInfos;
1059 settings.apLostThreshold = apLostThreshold;
1060 mAsyncChannel.sendMessage(CMD_SET_HOTLIST, 0, key, settings);
1064 * remove tracking of interesting access points
1065 * @param listener same object provided in {@link #startTrackingBssids}
1067 public void stopTrackingBssids(BssidListener listener) {
1068 Preconditions.checkNotNull(listener, "listener cannot be null");
1069 int key = removeListener(listener);
1070 if (key == INVALID_KEY) return;
1072 mAsyncChannel.sendMessage(CMD_RESET_HOTLIST, 0, key);
1076 /* private members and methods */
1078 private static final String TAG = "WifiScanner";
1079 private static final boolean DBG = false;
1081 /* commands for Wifi Service */
1082 private static final int BASE = Protocol.BASE_WIFI_SCANNER;
1085 public static final int CMD_SCAN = BASE + 0;
1087 public static final int CMD_START_BACKGROUND_SCAN = BASE + 2;
1089 public static final int CMD_STOP_BACKGROUND_SCAN = BASE + 3;
1091 public static final int CMD_GET_SCAN_RESULTS = BASE + 4;
1093 public static final int CMD_SCAN_RESULT = BASE + 5;
1095 public static final int CMD_SET_HOTLIST = BASE + 6;
1097 public static final int CMD_RESET_HOTLIST = BASE + 7;
1099 public static final int CMD_AP_FOUND = BASE + 9;
1101 public static final int CMD_AP_LOST = BASE + 10;
1103 public static final int CMD_START_TRACKING_CHANGE = BASE + 11;
1105 public static final int CMD_STOP_TRACKING_CHANGE = BASE + 12;
1107 public static final int CMD_CONFIGURE_WIFI_CHANGE = BASE + 13;
1109 public static final int CMD_WIFI_CHANGE_DETECTED = BASE + 15;
1111 public static final int CMD_WIFI_CHANGES_STABILIZED = BASE + 16;
1113 public static final int CMD_OP_SUCCEEDED = BASE + 17;
1115 public static final int CMD_OP_FAILED = BASE + 18;
1117 public static final int CMD_PERIOD_CHANGED = BASE + 19;
1119 public static final int CMD_FULL_SCAN_RESULT = BASE + 20;
1121 public static final int CMD_START_SINGLE_SCAN = BASE + 21;
1123 public static final int CMD_STOP_SINGLE_SCAN = BASE + 22;
1125 public static final int CMD_SINGLE_SCAN_COMPLETED = BASE + 23;
1127 public static final int CMD_START_PNO_SCAN = BASE + 24;
1129 public static final int CMD_STOP_PNO_SCAN = BASE + 25;
1131 public static final int CMD_PNO_NETWORK_FOUND = BASE + 26;
1133 private Context mContext;
1134 private IWifiScanner mService;
1136 private static final int INVALID_KEY = 0;
1137 private int mListenerKey = 1;
1139 private final SparseArray mListenerMap = new SparseArray();
1140 private final Object mListenerMapLock = new Object();
1142 private AsyncChannel mAsyncChannel;
1143 private final Handler mInternalHandler;
1146 * Create a new WifiScanner instance.
1147 * Applications will almost always want to use
1148 * {@link android.content.Context#getSystemService Context.getSystemService()} to retrieve
1149 * the standard {@link android.content.Context#WIFI_SERVICE Context.WIFI_SERVICE}.
1150 * @param context the application context
1151 * @param service the Binder interface
1152 * @param looper the Looper used to deliver callbacks
1155 public WifiScanner(Context context, IWifiScanner service, Looper looper) {
1159 Messenger messenger = null;
1161 messenger = mService.getMessenger();
1162 } catch (RemoteException e) {
1163 throw e.rethrowFromSystemServer();
1166 if (messenger == null) {
1167 throw new IllegalStateException("getMessenger() returned null! This is invalid.");
1170 mAsyncChannel = new AsyncChannel();
1172 mInternalHandler = new ServiceHandler(looper);
1173 mAsyncChannel.connectSync(mContext, mInternalHandler, messenger);
1174 // We cannot use fullyConnectSync because it sends the FULL_CONNECTION message
1175 // synchronously, which causes WifiScanningService to receive the wrong replyTo value.
1176 mAsyncChannel.sendMessage(AsyncChannel.CMD_CHANNEL_FULL_CONNECTION);
1179 private void validateChannel() {
1180 if (mAsyncChannel == null) throw new IllegalStateException(
1181 "No permission to access and change wifi or a bad initialization");
1184 // Add a listener into listener map. If the listener already exists, return INVALID_KEY and
1185 // send an error message to internal handler; Otherwise add the listener to the listener map and
1186 // return the key of the listener.
1187 private int addListener(ActionListener listener) {
1188 synchronized (mListenerMapLock) {
1189 boolean keyExists = (getListenerKey(listener) != INVALID_KEY);
1190 // Note we need to put the listener into listener map even if it's a duplicate as the
1191 // internal handler will need the key to find the listener. In case of duplicates,
1192 // removing duplicate key logic will be handled in internal handler.
1193 int key = putListener(listener);
1195 if (DBG) Log.d(TAG, "listener key already exists");
1196 OperationResult operationResult = new OperationResult(REASON_DUPLICATE_REQEUST,
1197 "Outstanding request with same key not stopped yet");
1198 Message message = Message.obtain(mInternalHandler, CMD_OP_FAILED, 0, key,
1200 message.sendToTarget();
1208 private int putListener(Object listener) {
1209 if (listener == null) return INVALID_KEY;
1211 synchronized (mListenerMapLock) {
1213 key = mListenerKey++;
1214 } while (key == INVALID_KEY);
1215 mListenerMap.put(key, listener);
1220 private Object getListener(int key) {
1221 if (key == INVALID_KEY) return null;
1222 synchronized (mListenerMapLock) {
1223 Object listener = mListenerMap.get(key);
1228 private int getListenerKey(Object listener) {
1229 if (listener == null) return INVALID_KEY;
1230 synchronized (mListenerMapLock) {
1231 int index = mListenerMap.indexOfValue(listener);
1235 return mListenerMap.keyAt(index);
1240 private Object removeListener(int key) {
1241 if (key == INVALID_KEY) return null;
1242 synchronized (mListenerMapLock) {
1243 Object listener = mListenerMap.get(key);
1244 mListenerMap.remove(key);
1249 private int removeListener(Object listener) {
1250 int key = getListenerKey(listener);
1251 if (key == INVALID_KEY) {
1252 Log.e(TAG, "listener cannot be found");
1255 synchronized (mListenerMapLock) {
1256 mListenerMap.remove(key);
1262 public static class OperationResult implements Parcelable {
1264 public String description;
1266 public OperationResult(int reason, String description) {
1267 this.reason = reason;
1268 this.description = description;
1271 /** Implement the Parcelable interface {@hide} */
1272 public int describeContents() {
1276 /** Implement the Parcelable interface {@hide} */
1277 public void writeToParcel(Parcel dest, int flags) {
1278 dest.writeInt(reason);
1279 dest.writeString(description);
1282 /** Implement the Parcelable interface {@hide} */
1283 public static final Creator<OperationResult> CREATOR =
1284 new Creator<OperationResult>() {
1285 public OperationResult createFromParcel(Parcel in) {
1286 int reason = in.readInt();
1287 String description = in.readString();
1288 return new OperationResult(reason, description);
1291 public OperationResult[] newArray(int size) {
1292 return new OperationResult[size];
1297 private class ServiceHandler extends Handler {
1298 ServiceHandler(Looper looper) {
1302 public void handleMessage(Message msg) {
1304 case AsyncChannel.CMD_CHANNEL_FULLY_CONNECTED:
1306 case AsyncChannel.CMD_CHANNEL_DISCONNECTED:
1307 Log.e(TAG, "Channel connection lost");
1308 // This will cause all further async API calls on the WifiManager
1309 // to fail and throw an exception
1310 mAsyncChannel = null;
1315 Object listener = getListener(msg.arg2);
1317 if (listener == null) {
1318 if (DBG) Log.d(TAG, "invalid listener key = " + msg.arg2);
1321 if (DBG) Log.d(TAG, "listener key = " + msg.arg2);
1325 /* ActionListeners grouped together */
1326 case CMD_OP_SUCCEEDED :
1327 ((ActionListener) listener).onSuccess();
1329 case CMD_OP_FAILED : {
1330 OperationResult result = (OperationResult)msg.obj;
1331 ((ActionListener) listener).onFailure(result.reason, result.description);
1332 removeListener(msg.arg2);
1335 case CMD_SCAN_RESULT :
1336 ((ScanListener) listener).onResults(
1337 ((ParcelableScanData) msg.obj).getResults());
1339 case CMD_FULL_SCAN_RESULT :
1340 ScanResult result = (ScanResult) msg.obj;
1341 ((ScanListener) listener).onFullResult(result);
1343 case CMD_PERIOD_CHANGED:
1344 ((ScanListener) listener).onPeriodChanged(msg.arg1);
1347 ((BssidListener) listener).onFound(
1348 ((ParcelableScanResults) msg.obj).getResults());
1351 ((BssidListener) listener).onLost(
1352 ((ParcelableScanResults) msg.obj).getResults());
1354 case CMD_WIFI_CHANGE_DETECTED:
1355 ((WifiChangeListener) listener).onChanging(
1356 ((ParcelableScanResults) msg.obj).getResults());
1358 case CMD_WIFI_CHANGES_STABILIZED:
1359 ((WifiChangeListener) listener).onQuiescence(
1360 ((ParcelableScanResults) msg.obj).getResults());
1362 case CMD_SINGLE_SCAN_COMPLETED:
1363 if (DBG) Log.d(TAG, "removing listener for single scan");
1364 removeListener(msg.arg2);
1366 case CMD_PNO_NETWORK_FOUND:
1367 ((PnoScanListener) listener).onPnoNetworkFound(
1368 ((ParcelableScanResults) msg.obj).getResults());
1371 if (DBG) Log.d(TAG, "Ignoring message " + msg.what);