/* * 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 com.android.server.wifi; import android.app.AlarmManager; import android.app.PendingIntent; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.net.wifi.IWifiScanner; import android.net.wifi.ScanResult; import android.net.wifi.WifiManager; import android.net.wifi.WifiScanner; import android.net.wifi.WifiScanner.ScanSettings; import android.net.wifi.WifiSsid; import android.os.Bundle; import android.os.Handler; import android.os.HandlerThread; import android.os.Looper; import android.os.Message; import android.os.Messenger; import android.os.RemoteException; import android.os.SystemClock; import android.util.Log; import android.util.Slog; import com.android.internal.util.AsyncChannel; import com.android.internal.util.Protocol; import com.android.internal.util.StateMachine; import com.android.internal.util.State; import java.io.FileDescriptor; import java.io.PrintWriter; import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Map; public class WifiScanningServiceImpl extends IWifiScanner.Stub { private static final String TAG = "WifiScanningService"; private static final boolean DBG = true; private static final int INVALID_KEY = 0; // same as WifiScanner private static final int MIN_PERIOD_PER_CHANNEL_MS = 200; // DFS needs 120 ms @Override public Messenger getMessenger() { return new Messenger(mClientHandler); } @Override public Bundle getAvailableChannels(int band) { WifiScanner.ChannelSpec channelSpecs[] = getChannelsForBand(band); ArrayList list = new ArrayList(channelSpecs.length); for (WifiScanner.ChannelSpec channelSpec : channelSpecs) { list.add(channelSpec.frequency); } Bundle b = new Bundle(); b.putIntegerArrayList(WifiScanner.GET_AVAILABLE_CHANNELS_EXTRA, list); return b; } private void enforceConnectivityInternalPermission() { mContext.enforceCallingOrSelfPermission( android.Manifest.permission.CONNECTIVITY_INTERNAL, "WifiScanningServiceImpl"); } private class ClientHandler extends Handler { ClientHandler(android.os.Looper looper) { super(looper); } @Override public void handleMessage(Message msg) { if (DBG) Log.d(TAG, "ClientHandler got" + msg); switch (msg.what) { case AsyncChannel.CMD_CHANNEL_HALF_CONNECTED: if (msg.arg1 == AsyncChannel.STATUS_SUCCESSFUL) { AsyncChannel c = (AsyncChannel) msg.obj; if (DBG) Slog.d(TAG, "New client listening to asynchronous messages: " + msg.replyTo); ClientInfo cInfo = new ClientInfo(c, msg.replyTo); mClients.put(msg.replyTo, cInfo); } else { Slog.e(TAG, "Client connection failure, error=" + msg.arg1); } return; case AsyncChannel.CMD_CHANNEL_DISCONNECTED: if (msg.arg1 == AsyncChannel.STATUS_SEND_UNSUCCESSFUL) { Slog.e(TAG, "Send failed, client connection lost"); } else { if (DBG) Slog.d(TAG, "Client connection lost with reason: " + msg.arg1); } if (DBG) Slog.d(TAG, "closing client " + msg.replyTo); ClientInfo ci = mClients.remove(msg.replyTo); if (ci != null) { /* can be null if send failed above */ ci.cleanup(); } return; case AsyncChannel.CMD_CHANNEL_FULL_CONNECTION: AsyncChannel ac = new AsyncChannel(); ac.connect(mContext, this, msg.replyTo); return; } ClientInfo ci = mClients.get(msg.replyTo); if (ci == null) { Slog.e(TAG, "Could not find client info for message " + msg.replyTo); replyFailed(msg, WifiScanner.REASON_INVALID_LISTENER, "Could not find listener"); return; } try { enforceConnectivityInternalPermission(); } catch (SecurityException e) { replyFailed(msg, WifiScanner.REASON_NOT_AUTHORIZED, "Not authorized"); return; } int validCommands[] = { WifiScanner.CMD_SCAN, WifiScanner.CMD_START_BACKGROUND_SCAN, WifiScanner.CMD_STOP_BACKGROUND_SCAN, WifiScanner.CMD_SET_HOTLIST, WifiScanner.CMD_RESET_HOTLIST, WifiScanner.CMD_CONFIGURE_WIFI_CHANGE, WifiScanner.CMD_START_TRACKING_CHANGE, WifiScanner.CMD_STOP_TRACKING_CHANGE }; for (int cmd : validCommands) { if (cmd == msg.what) { mStateMachine.sendMessage(Message.obtain(msg)); return; } } replyFailed(msg, WifiScanner.REASON_INVALID_REQUEST, "Invalid request"); } } private static final int BASE = Protocol.BASE_WIFI_SCANNER_SERVICE; private static final int CMD_SCAN_RESULTS_AVAILABLE = BASE + 0; private static final int CMD_FULL_SCAN_RESULTS = BASE + 1; private static final int CMD_HOTLIST_AP_FOUND = BASE + 2; private static final int CMD_HOTLIST_AP_LOST = BASE + 3; private static final int CMD_WIFI_CHANGE_DETECTED = BASE + 4; private static final int CMD_WIFI_CHANGES_STABILIZED = BASE + 5; private static final int CMD_DRIVER_LOADED = BASE + 6; private static final int CMD_DRIVER_UNLOADED = BASE + 7; private static final int CMD_SCAN_PAUSED = BASE + 8; private static final int CMD_SCAN_RESTARTED = BASE + 9; private Context mContext; private WifiScanningStateMachine mStateMachine; private ClientHandler mClientHandler; WifiScanningServiceImpl() { } WifiScanningServiceImpl(Context context) { mContext = context; } public void startService(Context context) { mContext = context; HandlerThread thread = new HandlerThread("WifiScanningService"); thread.start(); mClientHandler = new ClientHandler(thread.getLooper()); mStateMachine = new WifiScanningStateMachine(thread.getLooper()); mWifiChangeStateMachine = new WifiChangeStateMachine(thread.getLooper()); mContext.registerReceiver( new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { int state = intent.getIntExtra( WifiManager.EXTRA_SCAN_AVAILABLE, WifiManager.WIFI_STATE_DISABLED); if (DBG) Log.d(TAG, "SCAN_AVAILABLE : " + state); if (state == WifiManager.WIFI_STATE_ENABLED) { mStateMachine.sendMessage(CMD_DRIVER_LOADED); } else if (state == WifiManager.WIFI_STATE_DISABLED) { mStateMachine.sendMessage(CMD_DRIVER_UNLOADED); } } }, new IntentFilter(WifiManager.WIFI_SCAN_AVAILABLE)); mStateMachine.start(); mWifiChangeStateMachine.start(); } class WifiScanningStateMachine extends StateMachine implements WifiNative.ScanEventHandler, WifiNative.HotlistEventHandler, WifiNative.SignificantWifiChangeEventHandler { private final DefaultState mDefaultState = new DefaultState(); private final StartedState mStartedState = new StartedState(); private final PausedState mPausedState = new PausedState(); public WifiScanningStateMachine(Looper looper) { super(TAG, looper); setLogRecSize(512); setLogOnlyTransitions(false); // setDbg(DBG); addState(mDefaultState); addState(mStartedState, mDefaultState); addState(mPausedState, mDefaultState); setInitialState(mDefaultState); } @Override public void onScanResultsAvailable() { if (DBG) Log.d(TAG, "onScanResultAvailable event received"); sendMessage(CMD_SCAN_RESULTS_AVAILABLE); } @Override public void onSingleScanComplete() { if (DBG) Log.d(TAG, "onSingleScanComplete event received"); sendMessage(CMD_SCAN_RESULTS_AVAILABLE); } @Override public void onFullScanResult(ScanResult fullScanResult) { if (DBG) Log.d(TAG, "Full scanresult received"); sendMessage(CMD_FULL_SCAN_RESULTS, 0, 0, fullScanResult); } @Override public void onScanPaused() { sendMessage(CMD_SCAN_PAUSED); } @Override public void onScanRestarted() { sendMessage(CMD_SCAN_RESTARTED); } @Override public void onHotlistApFound(ScanResult[] results) { if (DBG) Log.d(TAG, "HotlistApFound event received"); sendMessage(CMD_HOTLIST_AP_FOUND, 0, 0, results); } @Override public void onChangesFound(ScanResult[] results) { if (DBG) Log.d(TAG, "onWifiChangesFound event received"); sendMessage(CMD_WIFI_CHANGE_DETECTED, 0, 0, results); } class DefaultState extends State { @Override public void enter() { if (DBG) Log.d(TAG, "DefaultState"); } @Override public boolean processMessage(Message msg) { if (DBG) Log.d(TAG, "DefaultState got" + msg); ClientInfo ci = mClients.get(msg.replyTo); switch (msg.what) { case CMD_DRIVER_LOADED: if (WifiNative.startHal() && WifiNative.getInterfaces() != 0) { WifiNative.ScanCapabilities capabilities = new WifiNative.ScanCapabilities(); if (WifiNative.getScanCapabilities(capabilities)) { transitionTo(mStartedState); } else { loge("could not get scan capabilities"); } } else { loge("could not start HAL"); } break; case WifiScanner.CMD_SCAN: case WifiScanner.CMD_START_BACKGROUND_SCAN: case WifiScanner.CMD_STOP_BACKGROUND_SCAN: case WifiScanner.CMD_SET_HOTLIST: case WifiScanner.CMD_RESET_HOTLIST: case WifiScanner.CMD_CONFIGURE_WIFI_CHANGE: case WifiScanner.CMD_START_TRACKING_CHANGE: case WifiScanner.CMD_STOP_TRACKING_CHANGE: replyFailed(msg, WifiScanner.REASON_UNSPECIFIED, "not available"); break; case CMD_SCAN_RESULTS_AVAILABLE: if (DBG) log("ignored scan results available event"); break; case CMD_FULL_SCAN_RESULTS: if (DBG) log("ignored full scan result event"); break; default: break; } return HANDLED; } } class StartedState extends State { @Override public void enter() { if (DBG) Log.d(TAG, "StartedState"); } @Override public boolean processMessage(Message msg) { if (DBG) Log.d(TAG, "StartedState got" + msg); ClientInfo ci = mClients.get(msg.replyTo); switch (msg.what) { case CMD_DRIVER_UNLOADED: transitionTo(mDefaultState); break; case WifiScanner.CMD_SCAN: replyFailed(msg, WifiScanner.REASON_UNSPECIFIED, "not implemented"); break; case WifiScanner.CMD_START_BACKGROUND_SCAN: if (addScanRequest(ci, msg.arg2, (ScanSettings) msg.obj)) { replySucceeded(msg, null); } else { replyFailed(msg, WifiScanner.REASON_INVALID_REQUEST, "bad request"); } break; case WifiScanner.CMD_STOP_BACKGROUND_SCAN: removeScanRequest(ci, msg.arg2); break; case WifiScanner.CMD_GET_SCAN_RESULTS: replySucceeded(msg, getScanResults(ci)); break; case WifiScanner.CMD_SET_HOTLIST: setHotlist(ci, msg.arg2, (WifiScanner.HotlistSettings) msg.obj); replySucceeded(msg, null); break; case WifiScanner.CMD_RESET_HOTLIST: resetHotlist(ci, msg.arg2); break; case WifiScanner.CMD_START_TRACKING_CHANGE: trackWifiChanges(ci, msg.arg2); replySucceeded(msg, null); break; case WifiScanner.CMD_STOP_TRACKING_CHANGE: untrackWifiChanges(ci, msg.arg2); break; case WifiScanner.CMD_CONFIGURE_WIFI_CHANGE: configureWifiChange((WifiScanner.WifiChangeSettings) msg.obj); break; case CMD_SCAN_RESULTS_AVAILABLE: { ScanResult[] results = WifiNative.getScanResults(); Collection clients = mClients.values(); for (ClientInfo ci2 : clients) { ci2.reportScanResults(results); } } break; case CMD_FULL_SCAN_RESULTS: { ScanResult result = (ScanResult) msg.obj; if (DBG) Log.d(TAG, "reporting fullscan result for " + result.SSID); Collection clients = mClients.values(); for (ClientInfo ci2 : clients) { ci2.reportFullScanResult(result); } } break; case CMD_HOTLIST_AP_FOUND: { ScanResult[] results = (ScanResult[])msg.obj; if (DBG) Log.d(TAG, "Found " + results.length + " results"); Collection clients = mClients.values(); for (ClientInfo ci2 : clients) { ci2.reportHotlistResults(results); } } break; case CMD_WIFI_CHANGE_DETECTED: { ScanResult[] results = (ScanResult[])msg.obj; reportWifiChanged(results); } break; case CMD_WIFI_CHANGES_STABILIZED: { ScanResult[] results = (ScanResult[])msg.obj; reportWifiStabilized(results); } break; default: return NOT_HANDLED; } return HANDLED; } } class PausedState extends State { @Override public void enter() { if (DBG) Log.d(TAG, "PausedState"); } @Override public boolean processMessage(Message msg) { if (DBG) Log.d(TAG, "PausedState got" + msg); switch (msg.what) { case CMD_SCAN_RESTARTED: transitionTo(mStartedState); break; default: deferMessage(msg); break; } return HANDLED; } } @Override public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { super.dump(fd, pw, args); pw.println("number of clients : " + mClients.size()); pw.println(); } } /* client management */ HashMap mClients = new HashMap(); private class ClientInfo { private static final int MAX_LIMIT = 16; private final AsyncChannel mChannel; private final Messenger mMessenger; ClientInfo(AsyncChannel c, Messenger m) { mChannel = c; mMessenger = m; if (DBG) Slog.d(TAG, "New client, channel: " + c + " messenger: " + m); } @Override public String toString() { StringBuffer sb = new StringBuffer(); sb.append("mChannel ").append(mChannel).append("\n"); sb.append("mMessenger ").append(mMessenger).append("\n"); Iterator> it = mScanSettings.entrySet().iterator(); for (; it.hasNext(); ) { Map.Entry entry = it.next(); sb.append("[ScanId ").append(entry.getKey()).append("\n"); sb.append("ScanSettings ").append(entry.getValue()).append("\n"); sb.append("]"); } return sb.toString(); } HashMap mScanSettings = new HashMap(4); HashMap mScanPeriods = new HashMap(4); void addScanRequest(ScanSettings settings, int id) { mScanSettings.put(id, settings); } void removeScanRequest(int id) { mScanSettings.remove(id); } Iterator> getScans() { return mScanSettings.entrySet().iterator(); } Collection getScanSettings() { return mScanSettings.values(); } void reportScanResults(ScanResult[] results) { Iterator it = mScanSettings.keySet().iterator(); while (it.hasNext()) { int handler = it.next(); reportScanResults(results, handler); } } void reportScanResults(ScanResult[] results, int handler) { ScanSettings settings = mScanSettings.get(handler); WifiScanner.ChannelSpec desiredChannels[] = settings.channels; if (settings.band != WifiScanner.WIFI_BAND_UNSPECIFIED || desiredChannels == null || desiredChannels.length == 0) { desiredChannels = getChannelsForBand(settings.band); } // check the channels this client asked for .. int num_results = 0; for (ScanResult result : results) { for (WifiScanner.ChannelSpec channelSpec : desiredChannels) { if (channelSpec.frequency == result.frequency) { num_results++; break; } } } if (num_results == 0) { // nothing to report return; } ScanResult results2[] = new ScanResult[num_results]; int index = 0; for (ScanResult result : results) { for (WifiScanner.ChannelSpec channelSpec : desiredChannels) { if (channelSpec.frequency == result.frequency) { WifiSsid wifiSsid = WifiSsid.createFromAsciiEncoded(result.SSID); ScanResult newResult = new ScanResult(wifiSsid, result.BSSID, "", result.level, result.frequency, result.timestamp); results2[index] = newResult; index++; break; } } } deliverScanResults(handler, results2); } void deliverScanResults(int handler, ScanResult results[]) { WifiScanner.ParcelableScanResults parcelableScanResults = new WifiScanner.ParcelableScanResults(results); mChannel.sendMessage(WifiScanner.CMD_SCAN_RESULT, 0, handler, parcelableScanResults); } void reportFullScanResult(ScanResult result) { Iterator it = mScanSettings.keySet().iterator(); while (it.hasNext()) { int handler = it.next(); ScanSettings settings = mScanSettings.get(handler); WifiScanner.ChannelSpec desiredChannels[] = settings.channels; if (settings.band != WifiScanner.WIFI_BAND_UNSPECIFIED || desiredChannels == null || desiredChannels.length == 0) { desiredChannels = getChannelsForBand(settings.band); } for (WifiScanner.ChannelSpec channelSpec : desiredChannels) { if (channelSpec.frequency == result.frequency) { WifiSsid wifiSsid = WifiSsid.createFromAsciiEncoded(result.SSID); ScanResult newResult = new ScanResult(wifiSsid, result.BSSID, "", result.level, result.frequency, result.timestamp); if (DBG) Log.d(TAG, "sending it to " + handler); newResult.informationElements = result.informationElements.clone(); mChannel.sendMessage( WifiScanner.CMD_FULL_SCAN_RESULT, 0, handler, newResult); } } } } void reportPeriodChanged(int handler, ScanSettings settings, int newPeriodInMs) { Integer prevPeriodObject = mScanPeriods.get(handler); int prevPeriodInMs = settings.periodInMs; if (prevPeriodObject != null) { prevPeriodInMs = prevPeriodObject; } if (prevPeriodInMs != newPeriodInMs) { mChannel.sendMessage(WifiScanner.CMD_PERIOD_CHANGED, newPeriodInMs, handler); } } HashMap mHotlistSettings = new HashMap(); void addHostlistSettings(WifiScanner.HotlistSettings settings, int handler) { mHotlistSettings.put(handler, settings); } void removeHostlistSettings(int handler) { mHotlistSettings.remove(handler); } Collection getHotlistSettings() { return mHotlistSettings.values(); } void reportHotlistResults(ScanResult[] results) { Iterator> it = mHotlistSettings.entrySet().iterator(); while (it.hasNext()) { Map.Entry entry = it.next(); int handler = entry.getKey(); WifiScanner.HotlistSettings settings = entry.getValue(); int num_results = 0; for (ScanResult result : results) { for (WifiScanner.BssidInfo BssidInfo : settings.bssidInfos) { if (result.BSSID.equalsIgnoreCase(BssidInfo.bssid)) { num_results++; break; } } } if (num_results == 0) { // nothing to report return; } ScanResult results2[] = new ScanResult[num_results]; int index = 0; for (ScanResult result : results) { for (WifiScanner.BssidInfo BssidInfo : settings.bssidInfos) { if (result.BSSID.equalsIgnoreCase(BssidInfo.bssid)) { results2[index] = result; index++; } } } WifiScanner.ParcelableScanResults parcelableScanResults = new WifiScanner.ParcelableScanResults(results2); mChannel.sendMessage(WifiScanner.CMD_AP_FOUND, 0, handler, parcelableScanResults); } } HashSet mSignificantWifiHandlers = new HashSet(); void addSignificantWifiChange(int handler) { mSignificantWifiHandlers.add(handler); } void removeSignificantWifiChange(int handler) { mSignificantWifiHandlers.remove(handler); } Collection getWifiChangeHandlers() { return mSignificantWifiHandlers; } void reportWifiChanged(ScanResult[] results) { WifiScanner.ParcelableScanResults parcelableScanResults = new WifiScanner.ParcelableScanResults(results); Iterator it = mSignificantWifiHandlers.iterator(); while (it.hasNext()) { int handler = it.next(); mChannel.sendMessage(WifiScanner.CMD_WIFI_CHANGE_DETECTED, 0, handler, parcelableScanResults); } } void reportWifiStabilized(ScanResult[] results) { WifiScanner.ParcelableScanResults parcelableScanResults = new WifiScanner.ParcelableScanResults(results); Iterator it = mSignificantWifiHandlers.iterator(); while (it.hasNext()) { int handler = it.next(); mChannel.sendMessage(WifiScanner.CMD_WIFI_CHANGES_STABILIZED, 0, handler, parcelableScanResults); } } void cleanup() { mScanSettings.clear(); resetBuckets(); mHotlistSettings.clear(); resetHotlist(); for (Integer handler : mSignificantWifiHandlers) { untrackWifiChanges(this, handler); } mSignificantWifiHandlers.clear(); Log.d(TAG, "Successfully stopped all requests for client " + this); } } void replySucceeded(Message msg, Object obj) { if (msg.replyTo != null) { Message reply = Message.obtain(); reply.what = WifiScanner.CMD_OP_SUCCEEDED; reply.arg2 = msg.arg2; reply.obj = obj; try { msg.replyTo.send(reply); } catch (RemoteException e) { // There's not much we can do if reply can't be sent! } } else { // locally generated message; doesn't need a reply! } } void replyFailed(Message msg, int reason, String description) { if (msg.replyTo != null) { Message reply = Message.obtain(); reply.what = WifiScanner.CMD_OP_FAILED; reply.arg2 = msg.arg2; reply.obj = new WifiScanner.OperationResult(reason, description); try { msg.replyTo.send(reply); } catch (RemoteException e) { // There's not much we can do if reply can't be sent! } } else { // locally generated message; doesn't need a reply! } } private static class SettingsComputer { private static class TimeBucket { int periodInSecond; int periodMinInSecond; int periodMaxInSecond; TimeBucket(int p, int min, int max) { periodInSecond = p; periodMinInSecond = min; periodMaxInSecond = max; } } private static final TimeBucket[] mTimeBuckets = new TimeBucket[] { new TimeBucket( 1, 0, 5 ), new TimeBucket( 5, 5, 10 ), new TimeBucket( 10, 10, 25 ), new TimeBucket( 30, 25, 55 ), new TimeBucket( 60, 55, 100), new TimeBucket( 300, 240, 500), new TimeBucket( 600, 500, 1500), new TimeBucket( 1800, 1500, WifiScanner.MAX_SCAN_PERIOD_MS) }; private static final int MAX_BUCKETS = 8; private static final int MAX_CHANNELS = 8; private static final int DEFAULT_MAX_AP_PER_SCAN = 10; private static final int DEFAULT_REPORT_THRESHOLD = 10; private static final int DEFAULT_BASE_PERIOD_MS = 5000; private WifiNative.ScanSettings mSettings; { mSettings = new WifiNative.ScanSettings(); mSettings.max_ap_per_scan = DEFAULT_MAX_AP_PER_SCAN; mSettings.base_period_ms = DEFAULT_BASE_PERIOD_MS; mSettings.report_threshold = DEFAULT_REPORT_THRESHOLD; mSettings.buckets = new WifiNative.BucketSettings[MAX_BUCKETS]; for (int i = 0; i < mSettings.buckets.length; i++) { WifiNative.BucketSettings bucketSettings = new WifiNative.BucketSettings(); bucketSettings.bucket = i; bucketSettings.report_events = 0; bucketSettings.channels = new WifiNative.ChannelSettings[MAX_CHANNELS]; bucketSettings.num_channels = 0; for (int j = 0; j < bucketSettings.channels.length; j++) { WifiNative.ChannelSettings channelSettings = new WifiNative.ChannelSettings(); bucketSettings.channels[j] = channelSettings; } mSettings.buckets[i] = bucketSettings; } } HashMap mChannelToBucketMap = new HashMap(); private int getBestBucket(WifiScanner.ScanSettings settings) { // check to see if any of the channels are being scanned already // and find the smallest bucket index (it represents the quickest // period of scan) WifiScanner.ChannelSpec channels[] = settings.channels; if (channels == null) { // set channels based on band channels = getChannelsForBand(settings.band); } if (channels == null) { // still no channels; then there's nothing to scan Log.e(TAG, "No channels to scan!!"); return -1; } int mostFrequentBucketIndex = mTimeBuckets.length; for (WifiScanner.ChannelSpec desiredChannelSpec : channels) { if (mChannelToBucketMap.containsKey(desiredChannelSpec.frequency)) { int bucket = mChannelToBucketMap.get(desiredChannelSpec.frequency); if (bucket < mostFrequentBucketIndex) { mostFrequentBucketIndex = bucket; } } } int bestBucketIndex = -1; // best by period for (int i = 0; i < mTimeBuckets.length; i++) { TimeBucket bucket = mTimeBuckets[i]; if (bucket.periodMinInSecond * 1000 <= settings.periodInMs && settings.periodInMs < bucket.periodMaxInSecond * 1000) { // we set the time period to this bestBucketIndex = i; break; } } if (mostFrequentBucketIndex < bestBucketIndex) { for (WifiScanner.ChannelSpec desiredChannelSpec : channels) { mChannelToBucketMap.put(desiredChannelSpec.frequency, mostFrequentBucketIndex); } Log.d(TAG, "returning mf bucket number " + mostFrequentBucketIndex); return mostFrequentBucketIndex; } else if (bestBucketIndex != -1) { for (WifiScanner.ChannelSpec desiredChannelSpec : channels) { mChannelToBucketMap.put(desiredChannelSpec.frequency, bestBucketIndex); } Log.d(TAG, "returning best bucket number " + bestBucketIndex); return bestBucketIndex; } Log.e(TAG, "Could not find suitable bucket for period " + settings.periodInMs); return -1; } void prepChannelMap(WifiScanner.ScanSettings settings) { getBestBucket(settings); } int addScanRequestToBucket(WifiScanner.ScanSettings settings) { int bucketIndex = getBestBucket(settings); if (bucketIndex == -1) { Log.e(TAG, "Ignoring invalid settings"); return -1; } WifiScanner.ChannelSpec desiredChannels[] = settings.channels; if (settings.band != WifiScanner.WIFI_BAND_UNSPECIFIED || desiredChannels == null || desiredChannels.length == 0) { // set channels based on band desiredChannels = getChannelsForBand(settings.band); if (desiredChannels == null) { // still no channels; then there's nothing to scan Log.e(TAG, "No channels to scan!!"); return -1; } } // merge the channel lists for these buckets Log.d(TAG, "merging " + desiredChannels.length + " channels " + " for period " + settings.periodInMs); WifiNative.BucketSettings bucket = mSettings.buckets[bucketIndex]; boolean added = (bucket.num_channels == 0) && (bucket.band == WifiScanner.WIFI_BAND_UNSPECIFIED); Log.d(TAG, "existing " + bucket.num_channels + " channels "); HashSet newChannels = new HashSet(); for (WifiScanner.ChannelSpec desiredChannelSpec : desiredChannels) { Log.d(TAG, "desired channel " + desiredChannelSpec.frequency); boolean found = false; for (WifiNative.ChannelSettings existingChannelSpec : bucket.channels) { if (desiredChannelSpec.frequency == existingChannelSpec.frequency) { found = true; break; } } if (!found) { newChannels.add(desiredChannelSpec); } else { if (DBG) Log.d(TAG, "Already scanning channel " + desiredChannelSpec.frequency); } } if (settings.band != WifiScanner.WIFI_BAND_UNSPECIFIED || (bucket.num_channels + newChannels.size()) > bucket.channels.length) { // can't accommodate all channels; switch to specifying band bucket.num_channels = 0; bucket.band = getBandFromChannels(bucket.channels) | getBandFromChannels(desiredChannels); bucket.channels = new WifiNative.ChannelSettings[0]; Log.d(TAG, "switching to using band " + bucket.band); } else { for (WifiScanner.ChannelSpec desiredChannelSpec : newChannels) { Log.d(TAG, "adding new channel spec " + desiredChannelSpec.frequency); WifiNative.ChannelSettings channelSettings = bucket.channels[bucket.num_channels]; channelSettings.frequency = desiredChannelSpec.frequency; bucket.num_channels++; mChannelToBucketMap.put(bucketIndex, channelSettings.frequency); } } if (bucket.report_events < settings.reportEvents) { if (DBG) Log.d(TAG, "setting report_events to " + settings.reportEvents); bucket.report_events = settings.reportEvents; } else { if (DBG) Log.d(TAG, "report_events is " + settings.reportEvents); } if (added) { bucket.period_ms = mTimeBuckets[bucketIndex].periodInSecond * 1000; mSettings.num_buckets++; } if (mSettings.max_ap_per_scan < settings.numBssidsPerScan) { mSettings.max_ap_per_scan = settings.numBssidsPerScan; } return bucket.period_ms; } public WifiNative.ScanSettings getComputedSettings() { return mSettings; } public void compressBuckets() { int num_buckets = 0; for (int i = 0; i < mSettings.buckets.length; i++) { if (mSettings.buckets[i].num_channels != 0 || mSettings.buckets[i].band != WifiScanner.WIFI_BAND_UNSPECIFIED) { mSettings.buckets[num_buckets] = mSettings.buckets[i]; num_buckets++; } } // remove unused buckets for (int i = num_buckets; i < mSettings.buckets.length; i++) { mSettings.buckets[i] = null; } mSettings.num_buckets = num_buckets; if (num_buckets != 0) { mSettings.base_period_ms = mSettings.buckets[0].period_ms; } } } boolean resetBuckets() { SettingsComputer c = new SettingsComputer(); Collection clients = mClients.values(); for (ClientInfo ci : clients) { Collection settings = ci.getScanSettings(); for (ScanSettings s : settings) { c.prepChannelMap(s); } } for (ClientInfo ci : clients) { Iterator it = ci.getScans(); while (it.hasNext()) { Map.Entry entry = (Map.Entry)it.next(); int id = entry.getKey(); ScanSettings s = entry.getValue(); int newPeriodInMs = c.addScanRequestToBucket(s); if (newPeriodInMs == -1) { if (DBG) Log.d(TAG, "could not find a good bucket"); return false; } if (newPeriodInMs != s.periodInMs) { ci.reportPeriodChanged(id, s, newPeriodInMs); } } } c.compressBuckets(); WifiNative.ScanSettings s = c.getComputedSettings(); if (s.num_buckets == 0) { if (DBG) Log.d(TAG, "Stopping scan because there are no buckets"); WifiNative.stopScan(); return true; } else { if (WifiNative.startScan(s, mStateMachine)) { if (DBG) Log.d(TAG, "Successfully started scan of " + s.num_buckets + " buckets at" + "time = " + SystemClock.elapsedRealtimeNanos()/1000); return true; } else { if (DBG) Log.d(TAG, "Failed to start scan of " + s.num_buckets + " buckets"); return false; } } } boolean addScanRequest(ClientInfo ci, int handler, ScanSettings settings) { // sanity check the input if (settings.periodInMs < WifiScanner.MIN_SCAN_PERIOD_MS) { Log.d(TAG, "Failing scan request because periodInMs is " + settings.periodInMs); return false; } int minSupportedPeriodMs = 0; if (settings.channels != null) { minSupportedPeriodMs = settings.channels.length * MIN_PERIOD_PER_CHANNEL_MS; } else { if ((settings.band & WifiScanner.WIFI_BAND_24_GHZ) == 0) { /* 2.4 GHz band has 11 to 13 channels */ minSupportedPeriodMs += 1000; } if ((settings.band & WifiScanner.WIFI_BAND_5_GHZ) == 0) { /* 5 GHz band has another 10 channels */ minSupportedPeriodMs += 1000; } if ((settings.band & WifiScanner.WIFI_BAND_5_GHZ_DFS_ONLY) == 0) { /* DFS requires passive scan which takes longer time */ minSupportedPeriodMs += 2000; } } if (settings.periodInMs < minSupportedPeriodMs) { Log.d(TAG, "Failing scan request because minSupportedPeriodMs is " + minSupportedPeriodMs + " but the request wants " + settings.periodInMs); return false; } ci.addScanRequest(settings, handler); if (resetBuckets()) { return true; } else { ci.removeScanRequest(handler); Log.d(TAG, "Failing scan request because failed to reset scan"); return false; } } void removeScanRequest(ClientInfo ci, int handler) { ci.removeScanRequest(handler); resetBuckets(); } ScanResult[] getScanResults(ClientInfo ci) { ScanResult results[] = WifiNative.getScanResults(); ci.reportScanResults(results); return results; } void resetHotlist() { Collection clients = mClients.values(); int num_hotlist_ap = 0; for (ClientInfo ci : clients) { Collection c = ci.getHotlistSettings(); for (WifiScanner.HotlistSettings s : c) { num_hotlist_ap += s.bssidInfos.length; } } if (num_hotlist_ap == 0) { WifiNative.resetHotlist(); } else { WifiScanner.BssidInfo bssidInfos[] = new WifiScanner.BssidInfo[num_hotlist_ap]; int index = 0; for (ClientInfo ci : clients) { Collection settings = ci.getHotlistSettings(); for (WifiScanner.HotlistSettings s : settings) { for (int i = 0; i < s.bssidInfos.length; i++, index++) { bssidInfos[index] = s.bssidInfos[i]; } } } WifiScanner.HotlistSettings settings = new WifiScanner.HotlistSettings(); settings.bssidInfos = bssidInfos; settings.apLostThreshold = 3; WifiNative.setHotlist(settings, mStateMachine); } } void setHotlist(ClientInfo ci, int handler, WifiScanner.HotlistSettings settings) { ci.addHostlistSettings(settings, handler); resetHotlist(); } void resetHotlist(ClientInfo ci, int handler) { ci.removeHostlistSettings(handler); resetHotlist(); } WifiChangeStateMachine mWifiChangeStateMachine; void trackWifiChanges(ClientInfo ci, int handler) { mWifiChangeStateMachine.enable(); ci.addSignificantWifiChange(handler); } void untrackWifiChanges(ClientInfo ci, int handler) { ci.removeSignificantWifiChange(handler); Collection clients = mClients.values(); for (ClientInfo ci2 : clients) { if (ci2.getWifiChangeHandlers().size() != 0) { // there is at least one client watching for // significant changes; so nothing more to do return; } } // no more clients looking for significant wifi changes // no need to keep the state machine running; disable it mWifiChangeStateMachine.disable(); } void configureWifiChange(WifiScanner.WifiChangeSettings settings) { mWifiChangeStateMachine.configure(settings); } void reportWifiChanged(ScanResult results[]) { Collection clients = mClients.values(); for (ClientInfo ci : clients) { ci.reportWifiChanged(results); } } void reportWifiStabilized(ScanResult results[]) { Collection clients = mClients.values(); for (ClientInfo ci : clients) { ci.reportWifiStabilized(results); } } class WifiChangeStateMachine extends StateMachine implements WifiNative.SignificantWifiChangeEventHandler { private static final String TAG = "WifiChangeStateMachine"; private static final int WIFI_CHANGE_CMD_NEW_SCAN_RESULTS = 0; private static final int WIFI_CHANGE_CMD_CHANGE_DETECTED = 1; private static final int WIFI_CHANGE_CMD_CHANGE_TIMEOUT = 2; private static final int WIFI_CHANGE_CMD_ENABLE = 3; private static final int WIFI_CHANGE_CMD_DISABLE = 4; private static final int WIFI_CHANGE_CMD_CONFIGURE = 5; private static final int MAX_APS_TO_TRACK = 3; private static final int MOVING_SCAN_PERIOD_MS = 10000; private static final int STATIONARY_SCAN_PERIOD_MS = 5000; private static final int MOVING_STATE_TIMEOUT_MS = 30000; State mDefaultState = new DefaultState(); State mStationaryState = new StationaryState(); State mMovingState = new MovingState(); private static final String ACTION_TIMEOUT = "com.android.server.WifiScanningServiceImpl.action.TIMEOUT"; AlarmManager mAlarmManager; PendingIntent mTimeoutIntent; ScanResult mCurrentBssids[]; WifiChangeStateMachine(Looper looper) { super("SignificantChangeStateMachine", looper); mClients.put(null, mClientInfo); addState(mDefaultState); addState(mStationaryState, mDefaultState); addState(mMovingState, mDefaultState); setInitialState(mDefaultState); } public void enable() { if (mAlarmManager == null) { mAlarmManager = (AlarmManager) mContext.getSystemService(Context.ALARM_SERVICE); } if (mTimeoutIntent == null) { Intent intent = new Intent(ACTION_TIMEOUT, null); mTimeoutIntent = PendingIntent.getBroadcast(mContext, 0, intent, 0); mContext.registerReceiver( new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { sendMessage(WIFI_CHANGE_CMD_CHANGE_TIMEOUT); } }, new IntentFilter(ACTION_TIMEOUT)); } sendMessage(WIFI_CHANGE_CMD_ENABLE); } public void disable() { sendMessage(WIFI_CHANGE_CMD_DISABLE); } public void configure(WifiScanner.WifiChangeSettings settings) { sendMessage(WIFI_CHANGE_CMD_CONFIGURE, settings); } class DefaultState extends State { @Override public void enter() { if (DBG) Log.d(TAG, "Entering IdleState"); } @Override public boolean processMessage(Message msg) { if (DBG) Log.d(TAG, "DefaultState state got " + msg); switch (msg.what) { case WIFI_CHANGE_CMD_ENABLE : transitionTo(mMovingState); break; case WIFI_CHANGE_CMD_DISABLE: // nothing to do break; case WIFI_CHANGE_CMD_NEW_SCAN_RESULTS: // nothing to do break; case WIFI_CHANGE_CMD_CONFIGURE: /* save configuration till we transition to moving state */ deferMessage(msg); break; default: return NOT_HANDLED; } return HANDLED; } } class StationaryState extends State { @Override public void enter() { if (DBG) Log.d(TAG, "Entering StationaryState"); reportWifiStabilized(mCurrentBssids); } @Override public boolean processMessage(Message msg) { if (DBG) Log.d(TAG, "Stationary state got " + msg); switch (msg.what) { case WIFI_CHANGE_CMD_ENABLE : // do nothing break; case WIFI_CHANGE_CMD_CHANGE_DETECTED: if (DBG) Log.d(TAG, "Got wifi change detected"); reportWifiChanged((ScanResult[])msg.obj); transitionTo(mMovingState); break; case WIFI_CHANGE_CMD_DISABLE: if (DBG) Log.d(TAG, "Got Disable Wifi Change"); mCurrentBssids = null; removeScanRequest(); untrackSignificantWifiChange(); transitionTo(mDefaultState); break; case WIFI_CHANGE_CMD_CONFIGURE: /* save configuration till we transition to moving state */ deferMessage(msg); break; default: return NOT_HANDLED; } return HANDLED; } } class MovingState extends State { boolean mWifiChangeDetected = false; boolean mScanResultsPending = false; @Override public void enter() { if (DBG) Log.d(TAG, "Entering MovingState"); issueFullScan(); } @Override public boolean processMessage(Message msg) { if (DBG) Log.d(TAG, "MovingState state got " + msg); switch (msg.what) { case WIFI_CHANGE_CMD_ENABLE : // do nothing break; case WIFI_CHANGE_CMD_DISABLE: if (DBG) Log.d(TAG, "Got Disable Wifi Change"); mCurrentBssids = null; removeScanRequest(); untrackSignificantWifiChange(); transitionTo(mDefaultState); break; case WIFI_CHANGE_CMD_NEW_SCAN_RESULTS: if (DBG) Log.d(TAG, "Got scan results"); if (mScanResultsPending) { if (DBG) Log.d(TAG, "reconfiguring scan"); reconfigureScan((ScanResult[])msg.obj, STATIONARY_SCAN_PERIOD_MS); mWifiChangeDetected = false; mAlarmManager.setExact(AlarmManager.RTC_WAKEUP, System.currentTimeMillis() + MOVING_STATE_TIMEOUT_MS, mTimeoutIntent); mScanResultsPending = false; } break; case WIFI_CHANGE_CMD_CONFIGURE: if (DBG) Log.d(TAG, "Got configuration from app"); WifiScanner.WifiChangeSettings settings = (WifiScanner.WifiChangeSettings) msg.obj; reconfigureScan(settings); mWifiChangeDetected = false; long unchangedDelay = settings.unchangedSampleSize * settings.periodInMs; mAlarmManager.cancel(mTimeoutIntent); mAlarmManager.setExact(AlarmManager.RTC_WAKEUP, System.currentTimeMillis() + unchangedDelay, mTimeoutIntent); break; case WIFI_CHANGE_CMD_CHANGE_DETECTED: if (DBG) Log.d(TAG, "Change detected"); mAlarmManager.cancel(mTimeoutIntent); reportWifiChanged((ScanResult[])msg.obj); mWifiChangeDetected = true; issueFullScan(); break; case WIFI_CHANGE_CMD_CHANGE_TIMEOUT: if (DBG) Log.d(TAG, "Got timeout event"); if (mWifiChangeDetected == false) { transitionTo(mStationaryState); } break; default: return NOT_HANDLED; } return HANDLED; } @Override public void exit() { mAlarmManager.cancel(mTimeoutIntent); } void issueFullScan() { if (DBG) Log.d(TAG, "Issuing full scan"); WifiScanner.ScanSettings settings = new WifiScanner.ScanSettings(); settings.band = WifiScanner.WIFI_BAND_BOTH; settings.periodInMs = MOVING_SCAN_PERIOD_MS; settings.reportEvents = WifiScanner.REPORT_EVENT_AFTER_EACH_SCAN; addScanRequest(settings); mScanResultsPending = true; } } void reconfigureScan(ScanResult[] results, int period) { // find brightest APs and set them as sentinels if (results.length < MAX_APS_TO_TRACK) { Log.d(TAG, "too few APs (" + results.length + ") available to track wifi change"); return; } removeScanRequest(); // remove duplicate BSSIDs HashMap bssidToScanResult = new HashMap(); for (ScanResult result : results) { ScanResult saved = bssidToScanResult.get(result.BSSID); if (saved == null) { bssidToScanResult.put(result.BSSID, result); } else if (saved.level > result.level) { bssidToScanResult.put(result.BSSID, result); } } // find brightest BSSIDs ScanResult brightest[] = new ScanResult[MAX_APS_TO_TRACK]; Collection results2 = bssidToScanResult.values(); for (ScanResult result : results2) { for (int j = 0; j < brightest.length; j++) { if (brightest[j] == null || (brightest[j].level < result.level)) { for (int k = brightest.length; k > (j + 1); k--) { brightest[k - 1] = brightest[k - 2]; } brightest[j] = result; break; } } } // Get channels to scan for ArrayList channels = new ArrayList(); for (int i = 0; i < brightest.length; i++) { boolean found = false; for (int j = i + 1; j < brightest.length; j++) { if (brightest[j].frequency == brightest[i].frequency) { found = true; } } if (!found) { channels.add(brightest[i].frequency); } } if (DBG) Log.d(TAG, "Found " + channels.size() + " channels"); // set scanning schedule WifiScanner.ScanSettings settings = new WifiScanner.ScanSettings(); settings.band = WifiScanner.WIFI_BAND_UNSPECIFIED; settings.channels = new WifiScanner.ChannelSpec[channels.size()]; for (int i = 0; i < channels.size(); i++) { settings.channels[i] = new WifiScanner.ChannelSpec(channels.get(i)); } settings.periodInMs = period; addScanRequest(settings); WifiScanner.WifiChangeSettings settings2 = new WifiScanner.WifiChangeSettings(); settings2.rssiSampleSize = 3; settings2.lostApSampleSize = 3; settings2.unchangedSampleSize = 3; settings2.minApsBreachingThreshold = 2; settings2.bssidInfos = new WifiScanner.BssidInfo[brightest.length]; for (int i = 0; i < brightest.length; i++) { WifiScanner.BssidInfo BssidInfo = new WifiScanner.BssidInfo(); BssidInfo.bssid = brightest[i].BSSID; int threshold = (100 + brightest[i].level) / 32 + 2; BssidInfo.low = brightest[i].level - threshold; BssidInfo.high = brightest[i].level + threshold; settings2.bssidInfos[i] = BssidInfo; if (DBG) Log.d(TAG, "Setting bssid=" + BssidInfo.bssid + ", " + "low=" + BssidInfo.low + ", high=" + BssidInfo.high); } trackSignificantWifiChange(settings2); mCurrentBssids = brightest; } void reconfigureScan(WifiScanner.WifiChangeSettings settings) { if (settings.bssidInfos.length < MAX_APS_TO_TRACK) { Log.d(TAG, "too few APs (" + settings.bssidInfos.length + ") available to track wifi change"); return; } if (DBG) Log.d(TAG, "Setting configuration specified by app"); mCurrentBssids = new ScanResult[settings.bssidInfos.length]; HashSet channels = new HashSet(); for (int i = 0; i < settings.bssidInfos.length; i++) { ScanResult result = new ScanResult(); result.BSSID = settings.bssidInfos[i].bssid; mCurrentBssids[i] = result; channels.add(settings.bssidInfos[i].frequencyHint); } // cancel previous scan removeScanRequest(); // set new scanning schedule WifiScanner.ScanSettings settings2 = new WifiScanner.ScanSettings(); settings2.band = WifiScanner.WIFI_BAND_UNSPECIFIED; settings2.channels = new WifiScanner.ChannelSpec[channels.size()]; int i = 0; for (Integer channel : channels) { settings2.channels[i++] = new WifiScanner.ChannelSpec(channel); } settings2.periodInMs = settings.periodInMs; addScanRequest(settings2); // start tracking new APs trackSignificantWifiChange(settings); } class ClientInfoLocal extends ClientInfo { ClientInfoLocal() { super(null, null); } @Override void deliverScanResults(int handler, ScanResult results[]) { if (DBG) Log.d(TAG, "Delivering messages directly"); sendMessage(WIFI_CHANGE_CMD_NEW_SCAN_RESULTS, 0, 0, results); } @Override void reportPeriodChanged(int handler, ScanSettings settings, int newPeriodInMs) { // nothing to do; no one is listening for this } } @Override public void onChangesFound(ScanResult results[]) { sendMessage(WIFI_CHANGE_CMD_CHANGE_DETECTED, 0, 0, results); } ClientInfo mClientInfo = new ClientInfoLocal(); private static final int SCAN_COMMAND_ID = 1; void addScanRequest(WifiScanner.ScanSettings settings) { if (DBG) Log.d(TAG, "Starting scans"); Message msg = Message.obtain(); msg.what = WifiScanner.CMD_START_BACKGROUND_SCAN; msg.arg2 = SCAN_COMMAND_ID; msg.obj = settings; mClientHandler.sendMessage(msg); } void removeScanRequest() { if (DBG) Log.d(TAG, "Stopping scans"); Message msg = Message.obtain(); msg.what = WifiScanner.CMD_STOP_BACKGROUND_SCAN; msg.arg2 = SCAN_COMMAND_ID; mClientHandler.sendMessage(msg); } void trackSignificantWifiChange(WifiScanner.WifiChangeSettings settings) { WifiNative.untrackSignificantWifiChange(); WifiNative.trackSignificantWifiChange(settings, this); } void untrackSignificantWifiChange() { WifiNative.untrackSignificantWifiChange(); } } private static WifiScanner.ChannelSpec[] getChannelsForBand(int band) { int channels[] = WifiNative.getChannelsForBand(band); if (channels != null) { WifiScanner.ChannelSpec channelSpecs[] = new WifiScanner.ChannelSpec[channels.length]; for (int i = 0; i < channels.length; i++) { channelSpecs[i] = new WifiScanner.ChannelSpec(channels[i]); } return channelSpecs; } else { return new WifiScanner.ChannelSpec[0]; } } private static int getBandFromChannels(WifiScanner.ChannelSpec[] channels) { int band = WifiScanner.WIFI_BAND_UNSPECIFIED; for (WifiScanner.ChannelSpec channel : channels) { if (2400 <= channel.frequency && channel.frequency < 2500) { band |= WifiScanner.WIFI_BAND_24_GHZ; } else if (5100 <= channel.frequency && channel.frequency < 6000) { band |= WifiScanner.WIFI_BAND_5_GHZ; } else { /* TODO: Add DFS Range */ } } return band; } private static int getBandFromChannels(WifiNative.ChannelSettings[] channels) { int band = WifiScanner.WIFI_BAND_UNSPECIFIED; for (WifiNative.ChannelSettings channel : channels) { if (2400 <= channel.frequency && channel.frequency < 2500) { band |= WifiScanner.WIFI_BAND_24_GHZ; } else if (5100 <= channel.frequency && channel.frequency < 6000) { band |= WifiScanner.WIFI_BAND_5_GHZ; } else { /* TODO: Add DFS Range */ } } return band; } }