/* * 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; } } } }