From 011e1b35a64180d6f0234af8a3c2b70777eb9f39 Mon Sep 17 00:00:00 2001 From: Vinit Deshapnde Date: Wed, 7 May 2014 21:09:11 -0700 Subject: Initial implementation of WifiScanner This change implements basic functionality of WifiScanner. Following functionality is enabled 1. Scanning - specify a list of channels to scan 2. Significant change detection 3. AP hotlist Change-Id: Ieef75b96bdbbd3c7d9b9e698bd16e64d3b465254 --- wifi/java/android/net/wifi/WifiScanner.java | 584 ++++++++++++++++++++++++++++ 1 file changed, 584 insertions(+) create mode 100644 wifi/java/android/net/wifi/WifiScanner.java (limited to 'wifi/java/android/net/wifi/WifiScanner.java') diff --git a/wifi/java/android/net/wifi/WifiScanner.java b/wifi/java/android/net/wifi/WifiScanner.java new file mode 100644 index 0000000..e02e14c --- /dev/null +++ b/wifi/java/android/net/wifi/WifiScanner.java @@ -0,0 +1,584 @@ +/* + * Copyright (C) 2008 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.net.wifi; + +import android.content.Context; +import android.os.Handler; +import android.os.HandlerThread; +import android.os.Looper; +import android.os.Message; +import android.os.Messenger; +import android.os.Parcel; +import android.os.Parcelable; +import android.os.RemoteException; +import android.util.Log; +import android.util.SparseArray; + +import com.android.internal.util.AsyncChannel; +import com.android.internal.util.Protocol; + +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.CountDownLatch; + + +/** + * This class provides a way to scan the Wifi universe around the device + * Get an instance of this class by calling + * {@link android.content.Context#getSystemService(String) Context.getSystemService(Context + * .WIFI_SCANNING_SERVICE)}. + * @hide + */ +public class WifiScanner { + + public static final int WIFI_BAND_UNSPECIFIED = 0; /* not specified */ + public static final int WIFI_BAND_24_GHZ = 1; /* 2.4 GHz band */ + public static final int WIFI_BAND_5_GHZ = 2; /* 5 GHz band without DFS channels */ + public static final int WIFI_BAND_5_GHZ_DFS_ONLY = 4; /* 5 GHz band with DFS channels */ + public static final int WIFI_BAND_5_GHZ_WITH_DFS = 6; /* 5 GHz band with DFS channels */ + public static final int WIFI_BAND_BOTH = 3; /* both bands without DFS channels */ + public static final int WIFI_BAND_BOTH_WITH_DFS = 7; /* both bands with DFS channels */ + + public static final int MIN_SCAN_PERIOD_MS = 300; /* minimum supported period */ + public static final int MAX_SCAN_PERIOD_MS = 1024000; /* maximum supported period */ + + public static final int REASON_SUCCEEDED = 0; + public static final int REASON_UNSPECIFIED = -1; + public static final int REASON_INVALID_LISTENER = -2; + public static final int REASON_INVALID_REQUEST = -3; + public static final int REASON_CONFLICTING_REQUEST = -4; + + public static interface ActionListener { + public void onSuccess(Object result); + public void onFailure(int reason, Object exception); + } + + /** + * gives you all the possible channels; channel is specified as an + * integer with frequency in MHz i.e. channel 1 is 2412 + */ + public List getAvailableChannels(int band) { + return null; + } + + /** + * provides channel specification to the APIs + */ + public static class ChannelSpec { + public int frequency; + public boolean passive; /* ignored on DFS channels */ + public int dwellTimeMS; /* not supported for now */ + + public ChannelSpec(int frequency) { + this.frequency = frequency; + passive = false; + dwellTimeMS = 0; + } + } + + public static final int REPORT_EVENT_AFTER_BUFFER_FULL = 0; + public static final int REPORT_EVENT_AFTER_EACH_SCAN = 1; + public static final int REPORT_EVENT_FULL_SCAN_RESULT = 2; + + /** + * scan configuration parameters + */ + public static class ScanSettings implements Parcelable { + + public int band; /* ignore channels if specified */ + public ChannelSpec[] channels; /* list of channels to scan */ + public int periodInMs; /* period of scan */ + public int reportEvents; /* a valid REPORT_EVENT value */ + + /** Implement the Parcelable interface {@hide} */ + public int describeContents() { + return 0; + } + + /** Implement the Parcelable interface {@hide} */ + public void writeToParcel(Parcel dest, int flags) { + dest.writeInt(band); + dest.writeInt(periodInMs); + dest.writeInt(channels.length); + + for (int i = 0; i < channels.length; i++) { + dest.writeInt(channels[i].frequency); + dest.writeInt(channels[i].dwellTimeMS); + dest.writeInt(channels[i].passive ? 1 : 0); + } + } + + /** Implement the Parcelable interface {@hide} */ + public static final Creator CREATOR = + new Creator() { + public ScanSettings createFromParcel(Parcel in) { + + ScanSettings settings = new ScanSettings(); + settings.band = in.readInt(); + settings.periodInMs = in.readInt(); + int num_channels = in.readInt(); + settings.channels = new ChannelSpec[num_channels]; + for (int i = 0; i < num_channels; i++) { + int frequency = in.readInt(); + + ChannelSpec spec = new ChannelSpec(frequency); + spec.dwellTimeMS = in.readInt(); + spec.passive = in.readInt() == 1; + settings.channels[i] = spec; + } + + return settings; + } + + public ScanSettings[] newArray(int size) { + return new ScanSettings[size]; + } + }; + + } + + public static class InformationElement { + public int id; + public byte[] bytes; + } + + public static class FullScanResult { + public ScanResult result; + public InformationElement informationElements[]; + } + + /** @hide */ + public static class ParcelableScanResults implements Parcelable { + public ScanResult mResults[]; + + public ParcelableScanResults(ScanResult[] results) { + mResults = results; + } + + public ScanResult[] getResults() { + return mResults; + } + + /** Implement the Parcelable interface {@hide} */ + public int describeContents() { + return 0; + } + + /** Implement the Parcelable interface {@hide} */ + public void writeToParcel(Parcel dest, int flags) { + dest.writeInt(mResults.length); + for (int i = 0; i < mResults.length; i++) { + ScanResult result = mResults[i]; + result.writeToParcel(dest, flags); + } + } + + /** Implement the Parcelable interface {@hide} */ + public static final Creator CREATOR = + new Creator() { + public ParcelableScanResults createFromParcel(Parcel in) { + int n = in.readInt(); + ScanResult results[] = new ScanResult[n]; + for (int i = 0; i < n; i++) { + results[i] = ScanResult.CREATOR.createFromParcel(in); + } + return new ParcelableScanResults(results); + } + + public ParcelableScanResults[] newArray(int size) { + return new ParcelableScanResults[size]; + } + }; + } + + /** + * Framework is co-ordinating scans across multiple apps; so it may not give exactly the + * same period requested. The period granted is stated on the onSuccess() event; and + * onPeriodChanged() will be called if/when it is changed because of multiple conflicting + * requests. This is similar to the way timers are handled. + */ + public interface ScanListener extends ActionListener { + public void onPeriodChanged(int periodInMs); + public void onResults(ScanResult[] results); + public void onFullResult(FullScanResult fullScanResult); + } + + public void scan(ScanSettings settings, ScanListener listener) { + validateChannel(); + sAsyncChannel.sendMessage(CMD_SCAN, 0, putListener(listener), settings); + } + public void startBackgroundScan(ScanSettings settings, ScanListener listener) { + validateChannel(); + sAsyncChannel.sendMessage(CMD_START_BACKGROUND_SCAN, 0, putListener(listener), settings); + } + public void stopBackgroundScan(boolean flush, ScanListener listener) { + validateChannel(); + sAsyncChannel.sendMessage(CMD_STOP_BACKGROUND_SCAN, 0, removeListener(listener)); + } + public void retrieveScanResults(boolean flush, ScanListener listener) { + validateChannel(); + sAsyncChannel.sendMessage(CMD_GET_SCAN_RESULTS, 0, getListenerKey(listener)); + } + + public static class HotspotInfo { + public String bssid; + public int low; /* minimum RSSI */ + public int high; /* maximum RSSI */ + } + + public static class WifiChangeSettings { + public int rssiSampleSize; /* sample size for RSSI averaging */ + public int lostApSampleSize; /* samples to confirm AP's loss */ + public int unchangedSampleSize; /* samples to confirm no change */ + public int minApsBreachingThreshold; /* change threshold to trigger event */ + public HotspotInfo[] hotspotInfos; + } + + /* overrides the significant wifi change state machine configuration */ + public void configureSignificantWifiChange( + int rssiSampleSize, /* sample size for RSSI averaging */ + int lostApSampleSize, /* samples to confirm AP's loss */ + int unchangedSampleSize, /* samples to confirm no change */ + int minApsBreachingThreshold, /* change threshold to trigger event */ + HotspotInfo[] hotspotInfos /* signal thresholds to crosss */ + ) + { + validateChannel(); + WifiChangeSettings settings = new WifiChangeSettings(); + settings.rssiSampleSize = rssiSampleSize; + settings.lostApSampleSize = lostApSampleSize; + settings.unchangedSampleSize = unchangedSampleSize; + settings.minApsBreachingThreshold = minApsBreachingThreshold; + settings.hotspotInfos = hotspotInfos; + + sAsyncChannel.sendMessage(CMD_CONFIGURE_WIFI_CHANGE, 0, 0, settings); + } + + public interface SignificantWifiChangeListener extends ActionListener { + public void onChanging(ScanResult[] results); /* changes are found */ + public void onQuiescence(ScanResult[] results); /* changes settled down */ + } + + public void trackSignificantWifiChange(SignificantWifiChangeListener listener) { + validateChannel(); + sAsyncChannel.sendMessage(CMD_START_TRACKING_CHANGE, 0, putListener(listener)); + } + public void untrackSignificantWifiChange(SignificantWifiChangeListener listener) { + validateChannel(); + sAsyncChannel.sendMessage(CMD_STOP_TRACKING_CHANGE, 0, removeListener(listener)); + } + + public void configureSignificantWifiChange(WifiChangeSettings settings) { + validateChannel(); + sAsyncChannel.sendMessage(CMD_CONFIGURE_WIFI_CHANGE, 0, 0, settings); + } + + public static interface HotlistListener extends ActionListener { + public void onFound(ScanResult[] results); + } + + /** @hide */ + public static class HotlistSettings implements Parcelable { + public HotspotInfo[] hotspotInfos; + public int apLostThreshold; + + /** Implement the Parcelable interface {@hide} */ + public int describeContents() { + return 0; + } + + /** Implement the Parcelable interface {@hide} */ + public void writeToParcel(Parcel dest, int flags) { + dest.writeInt(apLostThreshold); + dest.writeInt(hotspotInfos.length); + for (int i = 0; i < hotspotInfos.length; i++) { + HotspotInfo info = hotspotInfos[i]; + dest.writeString(info.bssid); + dest.writeInt(info.low); + dest.writeInt(info.high); + } + } + + /** Implement the Parcelable interface {@hide} */ + public static final Creator CREATOR = + new Creator() { + public HotlistSettings createFromParcel(Parcel in) { + HotlistSettings settings = new HotlistSettings(); + settings.apLostThreshold = in.readInt(); + int n = in.readInt(); + settings.hotspotInfos = new HotspotInfo[n]; + for (int i = 0; i < n; i++) { + HotspotInfo info = new HotspotInfo(); + info.bssid = in.readString(); + info.low = in.readInt(); + info.high = in.readInt(); + settings.hotspotInfos[i] = info; + } + return settings; + } + + public HotlistSettings[] newArray(int size) { + return new HotlistSettings[size]; + } + }; + } + + public void setHotlist(HotspotInfo[] hotspots, + int apLostThreshold, HotlistListener listener) { + validateChannel(); + HotlistSettings settings = new HotlistSettings(); + settings.hotspotInfos = hotspots; + sAsyncChannel.sendMessage(CMD_SET_HOTLIST, 0, putListener(listener), settings); + } + + public void resetHotlist(HotlistListener listener) { + validateChannel(); + sAsyncChannel.sendMessage(CMD_RESET_HOTLIST, 0, removeListener(listener)); + } + + + /* private members and methods */ + + private static final String TAG = "WifiScanner"; + private static final boolean DBG = true; + + /* commands for Wifi Service */ + private static final int BASE = Protocol.BASE_WIFI_SCANNER; + + /** @hide */ + public static final int CMD_SCAN = BASE + 0; + /** @hide */ + public static final int CMD_START_BACKGROUND_SCAN = BASE + 2; + /** @hide */ + public static final int CMD_STOP_BACKGROUND_SCAN = BASE + 3; + /** @hide */ + public static final int CMD_GET_SCAN_RESULTS = BASE + 4; + /** @hide */ + public static final int CMD_SCAN_RESULT = BASE + 5; + /** @hide */ + public static final int CMD_SET_HOTLIST = BASE + 6; + /** @hide */ + public static final int CMD_RESET_HOTLIST = BASE + 7; + /** @hide */ + public static final int CMD_AP_FOUND = BASE + 9; + /** @hide */ + public static final int CMD_AP_LOST = BASE + 10; + /** @hide */ + public static final int CMD_START_TRACKING_CHANGE = BASE + 11; + /** @hide */ + public static final int CMD_STOP_TRACKING_CHANGE = BASE + 12; + /** @hide */ + public static final int CMD_CONFIGURE_WIFI_CHANGE = BASE + 13; + /** @hide */ + public static final int CMD_WIFI_CHANGE_DETECTED = BASE + 15; + /** @hide */ + public static final int CMD_WIFI_CHANGES_STABILIZED = BASE + 16; + /** @hide */ + public static final int CMD_OP_SUCCEEDED = BASE + 17; + /** @hide */ + public static final int CMD_OP_FAILED = BASE + 18; + /** @hide */ + public static final int CMD_PERIOD_CHANGED = BASE + 19; + /** @hide */ + public static final int CMD_FULL_SCAN_RESULT = BASE + 20; + + private Context mContext; + private IWifiScanner mService; + + private static final int INVALID_KEY = 0; + private static int sListenerKey = 1; + + private static final SparseArray sListenerMap = new SparseArray(); + private static final Object sListenerMapLock = new Object(); + + private static AsyncChannel sAsyncChannel; + private static CountDownLatch sConnected; + + private static final Object sThreadRefLock = new Object(); + private static int sThreadRefCount; + private static HandlerThread sHandlerThread; + + /** + * Create a new WifiScanner instance. + * Applications will almost always want to use + * {@link android.content.Context#getSystemService Context.getSystemService()} to retrieve + * the standard {@link android.content.Context#WIFI_SERVICE Context.WIFI_SERVICE}. + * @param context the application context + * @param service the Binder interface + * @hide + */ + public WifiScanner(Context context, IWifiScanner service) { + mContext = context; + mService = service; + init(); + } + + private void init() { + synchronized (sThreadRefLock) { + if (++sThreadRefCount == 1) { + Messenger messenger = null; + try { + messenger = mService.getMessenger(); + } catch (RemoteException e) { + /* do nothing */ + } catch (SecurityException e) { + /* do nothing */ + } + + if (messenger == null) { + sAsyncChannel = null; + return; + } + + sHandlerThread = new HandlerThread("WifiScanner"); + sAsyncChannel = new AsyncChannel(); + sConnected = new CountDownLatch(1); + + sHandlerThread.start(); + Handler handler = new ServiceHandler(sHandlerThread.getLooper()); + sAsyncChannel.connect(mContext, handler, messenger); + try { + sConnected.await(); + } catch (InterruptedException e) { + Log.e(TAG, "interrupted wait at init"); + } + } + } + } + + private void validateChannel() { + if (sAsyncChannel == null) throw new IllegalStateException( + "No permission to access and change wifi or a bad initialization"); + } + + private static int putListener(Object listener) { + if (listener == null) return INVALID_KEY; + int key; + synchronized (sListenerMapLock) { + do { + key = sListenerKey++; + } while (key == INVALID_KEY); + sListenerMap.put(key, listener); + } + return key; + } + + private static Object getListener(int key) { + if (key == INVALID_KEY) return null; + synchronized (sListenerMapLock) { + Object listener = sListenerMap.get(key); + return listener; + } + } + + private static int getListenerKey(Object listener) { + if (listener == null) return INVALID_KEY; + synchronized (sListenerMapLock) { + int index = sListenerMap.indexOfValue(listener); + if (index == -1) { + return INVALID_KEY; + } else { + return sListenerMap.keyAt(index); + } + } + } + + private static Object removeListener(int key) { + if (key == INVALID_KEY) return null; + synchronized (sListenerMapLock) { + Object listener = sListenerMap.get(key); + sListenerMap.remove(key); + return listener; + } + } + + private static int removeListener(Object listener) { + int key = getListenerKey(listener); + if (key == INVALID_KEY) return key; + synchronized (sListenerMapLock) { + sListenerMap.remove(key); + return key; + } + } + + private static class ServiceHandler extends Handler { + ServiceHandler(Looper looper) { + super(looper); + } + @Override + public void handleMessage(Message msg) { + switch (msg.what) { + case AsyncChannel.CMD_CHANNEL_HALF_CONNECTED: + if (msg.arg1 == AsyncChannel.STATUS_SUCCESSFUL) { + sAsyncChannel.sendMessage(AsyncChannel.CMD_CHANNEL_FULL_CONNECTION); + } else { + Log.e(TAG, "Failed to set up channel connection"); + // This will cause all further async API calls on the WifiManager + // to fail and throw an exception + sAsyncChannel = null; + } + sConnected.countDown(); + return; + case AsyncChannel.CMD_CHANNEL_FULLY_CONNECTED: + return; + case AsyncChannel.CMD_CHANNEL_DISCONNECTED: + Log.e(TAG, "Channel connection lost"); + // This will cause all further async API calls on the WifiManager + // to fail and throw an exception + sAsyncChannel = null; + getLooper().quit(); + return; + } + + Object listener = getListener(msg.arg2); + if (DBG) Log.d(TAG, "listener key = " + msg.arg2); + + switch (msg.what) { + /* ActionListeners grouped together */ + case CMD_OP_SUCCEEDED : + ((ActionListener) listener).onSuccess(msg.obj); + break; + case CMD_OP_FAILED : + ((ActionListener) listener).onFailure(msg.arg1, msg.obj); + break; + case CMD_SCAN_RESULT : + ((ScanListener) listener).onResults( + ((ParcelableScanResults) msg.obj).getResults()); + return; + case CMD_FULL_SCAN_RESULT : + FullScanResult result = (FullScanResult) msg.obj; + ((ScanListener) listener).onFullResult(result); + return; + case CMD_AP_FOUND: + ((HotlistListener) listener).onFound( + ((ParcelableScanResults) msg.obj).getResults()); + return; + case CMD_WIFI_CHANGE_DETECTED: + ((SignificantWifiChangeListener) listener).onChanging( + ((ParcelableScanResults) msg.obj).getResults()); + return; + case CMD_WIFI_CHANGES_STABILIZED: + ((SignificantWifiChangeListener) listener).onQuiescence( + ((ParcelableScanResults) msg.obj).getResults()); + return; + default: + if (DBG) Log.d(TAG, "Ignoring message " + msg.what); + return; + } + } + } +} -- cgit v1.1