| /* |
| * 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.Manifest; |
| 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.content.pm.PackageManager; |
| 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.BssidInfo; |
| import android.net.wifi.WifiScanner.ChannelSpec; |
| import android.net.wifi.WifiScanner.ScanData; |
| import android.net.wifi.WifiScanner.ScanSettings; |
| import android.net.wifi.WifiSsid; |
| import android.os.Binder; |
| 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.os.WorkSource; |
| import android.util.LocalLog; |
| import android.util.Log; |
| |
| import com.android.internal.app.IBatteryStats; |
| 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 com.android.server.am.BatteryStatsService; |
| |
| 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.Map; |
| |
| public class WifiScanningServiceImpl extends IWifiScanner.Stub { |
| |
| private static final String TAG = "WifiScanningService"; |
| private static final boolean DBG = false; |
| private static final boolean VDBG = false; |
| |
| private static final int INVALID_KEY = 0; // same as WifiScanner |
| private static final int MIN_PERIOD_PER_CHANNEL_MS = 200; // DFS needs 120 ms |
| private static final int UNKNOWN_PID = -1; |
| |
| private static final LocalLog mLocalLog = new LocalLog(1024); |
| |
| private static void localLog(String message) { |
| mLocalLog.log(message); |
| } |
| |
| private static void logw(String message) { |
| Log.w(TAG, message); |
| mLocalLog.log(message); |
| } |
| |
| private static void loge(String message) { |
| Log.e(TAG, message); |
| mLocalLog.log(message); |
| } |
| |
| @Override |
| public Messenger getMessenger() { |
| if (mClientHandler != null) { |
| return new Messenger(mClientHandler); |
| } else { |
| loge("WifiScanningServiceImpl trying to get messenger w/o initialization"); |
| return null; |
| } |
| } |
| |
| @Override |
| public Bundle getAvailableChannels(int band) { |
| ChannelSpec channelSpecs[] = getChannelsForBand(band); |
| ArrayList<Integer> list = new ArrayList<Integer>(channelSpecs.length); |
| for (ChannelSpec channelSpec : channelSpecs) { |
| list.add(channelSpec.frequency); |
| } |
| Bundle b = new Bundle(); |
| b.putIntegerArrayList(WifiScanner.GET_AVAILABLE_CHANNELS_EXTRA, list); |
| return b; |
| } |
| |
| private void enforceLocationHardwarePermission(int uid) { |
| mContext.enforcePermission( |
| Manifest.permission.LOCATION_HARDWARE, |
| UNKNOWN_PID, uid, |
| "LocationHardware"); |
| } |
| |
| private class ClientHandler extends Handler { |
| |
| ClientHandler(android.os.Looper looper) { |
| super(looper); |
| } |
| |
| @Override |
| public void handleMessage(Message msg) { |
| |
| if (DBG) localLog("ClientHandler got" + msg); |
| |
| switch (msg.what) { |
| |
| case AsyncChannel.CMD_CHANNEL_HALF_CONNECTED: |
| if (msg.arg1 != AsyncChannel.STATUS_SUCCESSFUL) { |
| loge("Client connection failure, error=" + msg.arg1); |
| } |
| return; |
| case AsyncChannel.CMD_CHANNEL_FULL_CONNECTION: |
| AsyncChannel ac = new AsyncChannel(); |
| ac.connect(mContext, this, msg.replyTo); |
| if (DBG) localLog("New client connected : " + msg.sendingUid + msg.replyTo); |
| ClientInfo cInfo = new ClientInfo(msg.sendingUid, ac, msg.replyTo); |
| mClients.put(msg.replyTo, cInfo); |
| return; |
| case AsyncChannel.CMD_CHANNEL_DISCONNECTED: |
| if (msg.arg1 == AsyncChannel.STATUS_SEND_UNSUCCESSFUL) { |
| loge("Send failed, client connection lost"); |
| } else { |
| if (DBG) localLog("Client connection lost with reason: " + msg.arg1); |
| } |
| if (DBG) localLog("closing client " + msg.replyTo); |
| ClientInfo ci = mClients.remove(msg.replyTo); |
| if (ci != null) { /* can be null if send failed above */ |
| if (DBG) localLog("closing client " + ci.mUid); |
| ci.cleanup(); |
| } |
| return; |
| } |
| |
| try { |
| enforceLocationHardwarePermission(msg.sendingUid); |
| } catch (SecurityException e) { |
| localLog("failed to authorize app: " + e); |
| replyFailed(msg, WifiScanner.REASON_NOT_AUTHORIZED, "Not authorized"); |
| return; |
| } |
| |
| if (msg.what == WifiScanner.CMD_GET_SCAN_RESULTS) { |
| mStateMachine.sendMessage(Message.obtain(msg)); |
| return; |
| } |
| ClientInfo ci = mClients.get(msg.replyTo); |
| if (ci == null) { |
| loge("Could not find client info for message " + msg.replyTo); |
| replyFailed(msg, WifiScanner.REASON_INVALID_LISTENER, "Could not find listener"); |
| return; |
| } |
| |
| int validCommands[] = { |
| WifiScanner.CMD_SCAN, |
| WifiScanner.CMD_START_BACKGROUND_SCAN, |
| WifiScanner.CMD_STOP_BACKGROUND_SCAN, |
| WifiScanner.CMD_START_SINGLE_SCAN, |
| WifiScanner.CMD_STOP_SINGLE_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 static final int CMD_STOP_SCAN_INTERNAL = BASE + 10; |
| |
| private Context mContext; |
| private WifiScanningStateMachine mStateMachine; |
| private ClientHandler mClientHandler; |
| private IBatteryStats mBatteryStats; |
| private final WifiNative.ScanCapabilities mScanCapabilities = new WifiNative.ScanCapabilities(); |
| |
| 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()); |
| mBatteryStats = BatteryStatsService.getService(); |
| |
| 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) localLog("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) localLog("onScanResultAvailable event received"); |
| sendMessage(CMD_SCAN_RESULTS_AVAILABLE); |
| } |
| |
| @Override |
| public void onScanStatus() { |
| if (DBG) localLog("onScanStatus event received"); |
| sendMessage(CMD_SCAN_RESULTS_AVAILABLE); |
| } |
| |
| @Override |
| public void onFullScanResult(ScanResult fullScanResult) { |
| if (DBG) localLog("Full scanresult received"); |
| sendMessage(CMD_FULL_SCAN_RESULTS, 0, 0, fullScanResult); |
| } |
| |
| @Override |
| public void onScanPaused(ScanData scanData[]) { |
| sendMessage(CMD_SCAN_PAUSED, scanData); |
| } |
| |
| @Override |
| public void onScanRestarted() { |
| if (DBG) localLog("onScanRestarted() event received"); |
| sendMessage(CMD_SCAN_RESTARTED); |
| } |
| |
| @Override |
| public void onHotlistApFound(ScanResult[] results) { |
| if (DBG) localLog("HotlistApFound event received"); |
| sendMessage(CMD_HOTLIST_AP_FOUND, 0, 0, results); |
| } |
| |
| @Override |
| public void onHotlistApLost(ScanResult[] results) { |
| if (DBG) localLog("HotlistApLost event received"); |
| sendMessage(CMD_HOTLIST_AP_LOST, 0, 0, results); |
| } |
| |
| @Override |
| public void onChangesFound(ScanResult[] results) { |
| if (DBG) localLog("onWifiChangesFound event received"); |
| sendMessage(CMD_WIFI_CHANGE_DETECTED, 0, 0, results); |
| } |
| |
| class DefaultState extends State { |
| @Override |
| public void enter() { |
| if (DBG) localLog("DefaultState"); |
| } |
| @Override |
| public boolean processMessage(Message msg) { |
| |
| if (DBG) localLog("DefaultState got" + msg); |
| |
| ClientInfo ci = mClients.get(msg.replyTo); |
| |
| switch (msg.what) { |
| case CMD_DRIVER_LOADED: |
| if (WifiNative.getInterfaces() != 0) { |
| if (WifiNative.getScanCapabilities(mScanCapabilities)) { |
| 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_START_SINGLE_SCAN: |
| case WifiScanner.CMD_STOP_SINGLE_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: |
| case WifiScanner.CMD_GET_SCAN_RESULTS: |
| replyFailed(msg, WifiScanner.REASON_UNSPECIFIED, "not available"); |
| break; |
| |
| case CMD_SCAN_RESULTS_AVAILABLE: |
| if (DBG) localLog("ignored scan results available event"); |
| break; |
| |
| case CMD_FULL_SCAN_RESULTS: |
| if (DBG) localLog("ignored full scan result event"); |
| break; |
| |
| default: |
| break; |
| } |
| |
| return HANDLED; |
| } |
| } |
| |
| class StartedState extends State { |
| |
| @Override |
| public void enter() { |
| if (DBG) localLog("StartedState"); |
| } |
| |
| @Override |
| public boolean processMessage(Message msg) { |
| |
| if (DBG) localLog("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); |
| } 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: |
| reportScanResults(); |
| replySucceeded(msg); |
| break; |
| case WifiScanner.CMD_START_SINGLE_SCAN: |
| if (addSingleScanRequest(ci, msg.arg2, (ScanSettings) msg.obj)) { |
| replySucceeded(msg); |
| } else { |
| replyFailed(msg, WifiScanner.REASON_INVALID_REQUEST, "bad request"); |
| } |
| break; |
| case WifiScanner.CMD_STOP_SINGLE_SCAN: |
| removeScanRequest(ci, msg.arg2); |
| break; |
| case CMD_STOP_SCAN_INTERNAL: |
| localLog("Removing single shot scan"); |
| removeScanRequest((ClientInfo) msg.obj, msg.arg2); |
| break; |
| case WifiScanner.CMD_SET_HOTLIST: |
| setHotlist(ci, msg.arg2, (WifiScanner.HotlistSettings) msg.obj); |
| replySucceeded(msg); |
| break; |
| case WifiScanner.CMD_RESET_HOTLIST: |
| resetHotlist(ci, msg.arg2); |
| break; |
| case WifiScanner.CMD_START_TRACKING_CHANGE: |
| trackWifiChanges(ci, msg.arg2); |
| replySucceeded(msg); |
| 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: { |
| ScanData[] results = WifiNative.getScanResults(/* flush = */ true); |
| Collection<ClientInfo> clients = mClients.values(); |
| for (ClientInfo ci2 : clients) { |
| ci2.reportScanResults(results); |
| } |
| } |
| break; |
| case CMD_FULL_SCAN_RESULTS: { |
| ScanResult result = (ScanResult) msg.obj; |
| if (DBG) localLog("reporting fullscan result for " + result.SSID); |
| Collection<ClientInfo> clients = mClients.values(); |
| for (ClientInfo ci2 : clients) { |
| ci2.reportFullScanResult(result); |
| } |
| } |
| break; |
| |
| case CMD_HOTLIST_AP_FOUND: { |
| ScanResult[] results = (ScanResult[])msg.obj; |
| if (DBG) localLog("Found " + results.length + " results"); |
| Collection<ClientInfo> clients = mClients.values(); |
| for (ClientInfo ci2 : clients) { |
| ci2.reportHotlistResults(WifiScanner.CMD_AP_FOUND, results); |
| } |
| } |
| break; |
| case CMD_HOTLIST_AP_LOST: { |
| ScanResult[] results = (ScanResult[])msg.obj; |
| if (DBG) localLog("Lost " + results.length + " results"); |
| Collection<ClientInfo> clients = mClients.values(); |
| for (ClientInfo ci2 : clients) { |
| ci2.reportHotlistResults(WifiScanner.CMD_AP_LOST, 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; |
| case CMD_SCAN_PAUSED: { |
| ScanData results[] = (ScanData[]) msg.obj; |
| Collection<ClientInfo> clients = mClients.values(); |
| for (ClientInfo ci2 : clients) { |
| ci2.reportScanResults(results); |
| } |
| transitionTo(mPausedState); |
| } |
| break; |
| default: |
| return NOT_HANDLED; |
| } |
| |
| return HANDLED; |
| } |
| } |
| |
| class PausedState extends State { |
| @Override |
| public void enter() { |
| if (DBG) localLog("PausedState"); |
| } |
| |
| @Override |
| public boolean processMessage(Message msg) { |
| |
| if (DBG) localLog("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) { |
| pw.println("number of clients : " + mClients.size()); |
| for (ClientInfo client : mClients.values()) { |
| client.dump(fd, pw, args); |
| pw.append("------\n"); |
| } |
| pw.println(); |
| pw.println("localLog : "); |
| mLocalLog.dump(fd, pw, args); |
| pw.append("\n\n"); |
| super.dump(fd, pw, args); |
| } |
| } |
| |
| /* client management */ |
| HashMap<Messenger, ClientInfo> mClients = new HashMap<Messenger, ClientInfo>(); |
| |
| private class ClientInfo { |
| private static final int MAX_LIMIT = 16; |
| private final AsyncChannel mChannel; |
| private final Messenger mMessenger; |
| private final int mUid; |
| private final WorkSource mWorkSource; |
| private boolean mScanWorkReported = false; |
| |
| ClientInfo(int uid, AsyncChannel c, Messenger m) { |
| mChannel = c; |
| mMessenger = m; |
| mUid = uid; |
| mWorkSource = new WorkSource(uid, TAG); |
| if (DBG) localLog("New client, channel: " + c + " messenger: " + m); |
| } |
| |
| void reportBatchedScanStart() { |
| if (mUid == 0) |
| return; |
| |
| int csph = getCsph(); |
| |
| try { |
| mBatteryStats.noteWifiBatchedScanStartedFromSource(mWorkSource, csph); |
| localLog("started scanning for UID " + mUid + ", csph = " + csph); |
| } catch (RemoteException e) { |
| logw("failed to report scan work: " + e.toString()); |
| } |
| } |
| |
| void reportBatchedScanStop() { |
| if (mUid == 0) |
| return; |
| |
| try { |
| mBatteryStats.noteWifiBatchedScanStoppedFromSource(mWorkSource); |
| localLog("stopped scanning for UID " + mUid); |
| } catch (RemoteException e) { |
| logw("failed to cleanup scan work: " + e.toString()); |
| } |
| } |
| |
| int getCsph() { |
| int csph = 0; |
| for (ScanSettings settings : getScanSettings()) { |
| int num_channels = settings.channels == null ? 0 : settings.channels.length; |
| if (num_channels == 0 && settings.band != 0) { |
| num_channels = getChannelsForBand(settings.band).length; |
| } |
| |
| int scans_per_Hour = settings.periodInMs == 0 ? 1 : (3600 * 1000) / settings.periodInMs; |
| csph += num_channels * scans_per_Hour; |
| } |
| |
| return csph; |
| } |
| |
| void reportScanWorkUpdate() { |
| if (mScanWorkReported) { |
| reportBatchedScanStop(); |
| mScanWorkReported = false; |
| } |
| if (mScanSettings.isEmpty() == false) { |
| reportBatchedScanStart(); |
| mScanWorkReported = true; |
| } |
| } |
| |
| @Override |
| public String toString() { |
| StringBuffer sb = new StringBuffer(); |
| sb.append("mChannel ").append(mChannel).append("\n"); |
| sb.append("mMessenger ").append(mMessenger); |
| return sb.toString(); |
| } |
| |
| void dump(FileDescriptor fd, PrintWriter pw, String[] args) { |
| StringBuilder sb = new StringBuilder(); |
| sb.append(toString()); |
| |
| Iterator<Map.Entry<Integer, ScanSettings>> it = mScanSettings.entrySet().iterator(); |
| for (; it.hasNext(); ) { |
| Map.Entry<Integer, ScanSettings> entry = it.next(); |
| sb.append("ScanId ").append(entry.getKey()).append("\n"); |
| |
| ScanSettings scanSettings = entry.getValue(); |
| sb.append(describe(scanSettings)); |
| sb.append("\n"); |
| } |
| |
| pw.println(sb.toString()); |
| } |
| |
| HashMap<Integer, ScanSettings> mScanSettings = new HashMap<Integer, ScanSettings>(4); |
| HashMap<Integer, Integer> mScanPeriods = new HashMap<Integer, Integer>(4); |
| |
| void addScanRequest(ScanSettings settings, int id) { |
| mScanSettings.put(id, settings); |
| reportScanWorkUpdate(); |
| } |
| |
| void removeScanRequest(int id) { |
| ScanSettings settings = mScanSettings.remove(id); |
| if (settings != null && settings.periodInMs == 0) { |
| /* this was a single shot scan */ |
| mChannel.sendMessage(WifiScanner.CMD_SINGLE_SCAN_COMPLETED, 0, id); |
| } |
| reportScanWorkUpdate(); |
| } |
| |
| void removeAllScanRequests() { |
| Iterator<Map.Entry<Integer, ScanSettings>> it = mScanSettings.entrySet().iterator(); |
| while (it.hasNext()) { |
| Map.Entry<Integer, ScanSettings> entry = it.next(); |
| ScanSettings settings = entry.getValue(); |
| Log.d(TAG, "Pending scan removed, handler=" + entry.getKey() + |
| ", period=" + settings.periodInMs); |
| it.remove(); |
| } |
| } |
| |
| Iterator<Map.Entry<Integer, ScanSettings>> getScans() { |
| return mScanSettings.entrySet().iterator(); |
| } |
| |
| Collection<ScanSettings> getScanSettings() { |
| return mScanSettings.values(); |
| } |
| |
| void reportScanResults(ScanData[] results) { |
| Iterator<Integer> it = mScanSettings.keySet().iterator(); |
| while (it.hasNext()) { |
| int handler = it.next(); |
| reportScanResults(results, handler); |
| } |
| } |
| |
| void reportScanResults(ScanData[] results, int handler) { |
| ScanSettings settings = mScanSettings.get(handler); |
| 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 (ScanData result : results) { |
| boolean copyScanData = false; |
| for (ScanResult scanResult : result.getResults()) { |
| for (ChannelSpec channelSpec : desiredChannels) { |
| if (channelSpec.frequency == scanResult.frequency) { |
| copyScanData = true; |
| break; |
| } |
| } |
| if (copyScanData) { |
| num_results++; |
| break; |
| } |
| } |
| } |
| |
| localLog("results = " + results.length + ", num_results = " + num_results); |
| |
| ScanData results2[] = new ScanData[num_results]; |
| int index = 0; |
| for (ScanData result : results) { |
| boolean copyScanData = false; |
| for (ScanResult scanResult : result.getResults()) { |
| for (ChannelSpec channelSpec : desiredChannels) { |
| if (channelSpec.frequency == scanResult.frequency) { |
| copyScanData = true; |
| break; |
| } |
| } |
| if (copyScanData) { |
| break; |
| } |
| } |
| |
| if (copyScanData) { |
| if (VDBG) { |
| localLog("adding at " + index); |
| } |
| results2[index] = new WifiScanner.ScanData(result); |
| index++; |
| } |
| } |
| |
| localLog("delivering results, num = " + results2.length); |
| |
| deliverScanResults(handler, results2); |
| if (settings.periodInMs == 0) { |
| /* this is a single shot scan; stop the scan now */ |
| mStateMachine.sendMessage(CMD_STOP_SCAN_INTERNAL, 0, handler, this); |
| } |
| } |
| |
| void deliverScanResults(int handler, ScanData results[]) { |
| WifiScanner.ParcelableScanData parcelableScanData = |
| new WifiScanner.ParcelableScanData(results); |
| mChannel.sendMessage(WifiScanner.CMD_SCAN_RESULT, 0, handler, parcelableScanData); |
| } |
| |
| void reportFullScanResult(ScanResult result) { |
| Iterator<Integer> it = mScanSettings.keySet().iterator(); |
| while (it.hasNext()) { |
| int handler = it.next(); |
| ScanSettings settings = mScanSettings.get(handler); |
| ChannelSpec desiredChannels[] = settings.channels; |
| if (settings.band != WifiScanner.WIFI_BAND_UNSPECIFIED |
| || desiredChannels == null || desiredChannels.length == 0) { |
| desiredChannels = getChannelsForBand(settings.band); |
| } |
| for (ChannelSpec channelSpec : desiredChannels) { |
| if (channelSpec.frequency == result.frequency) { |
| ScanResult newResult = new ScanResult(result); |
| if (DBG) localLog("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<Integer, WifiScanner.HotlistSettings> mHotlistSettings = |
| new HashMap<Integer, WifiScanner.HotlistSettings>(); |
| |
| void addHostlistSettings(WifiScanner.HotlistSettings settings, int handler) { |
| mHotlistSettings.put(handler, settings); |
| } |
| |
| void removeHostlistSettings(int handler) { |
| mHotlistSettings.remove(handler); |
| } |
| |
| Collection<WifiScanner.HotlistSettings> getHotlistSettings() { |
| return mHotlistSettings.values(); |
| } |
| |
| void reportHotlistResults(int what, ScanResult[] results) { |
| Iterator<Map.Entry<Integer, WifiScanner.HotlistSettings>> it = |
| mHotlistSettings.entrySet().iterator(); |
| while (it.hasNext()) { |
| Map.Entry<Integer, WifiScanner.HotlistSettings> entry = it.next(); |
| int handler = entry.getKey(); |
| WifiScanner.HotlistSettings settings = entry.getValue(); |
| int num_results = 0; |
| for (ScanResult result : results) { |
| for (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 (BssidInfo BssidInfo : settings.bssidInfos) { |
| if (result.BSSID.equalsIgnoreCase(BssidInfo.bssid)) { |
| results2[index] = result; |
| index++; |
| } |
| } |
| } |
| |
| WifiScanner.ParcelableScanResults parcelableScanResults = |
| new WifiScanner.ParcelableScanResults(results2); |
| |
| mChannel.sendMessage(what, 0, handler, parcelableScanResults); |
| } |
| } |
| |
| HashSet<Integer> mSignificantWifiHandlers = new HashSet<Integer>(); |
| void addSignificantWifiChange(int handler) { |
| mSignificantWifiHandlers.add(handler); |
| } |
| |
| void removeSignificantWifiChange(int handler) { |
| mSignificantWifiHandlers.remove(handler); |
| } |
| |
| Collection<Integer> getWifiChangeHandlers() { |
| return mSignificantWifiHandlers; |
| } |
| |
| void reportWifiChanged(ScanResult[] results) { |
| WifiScanner.ParcelableScanResults parcelableScanResults = |
| new WifiScanner.ParcelableScanResults(results); |
| Iterator<Integer> 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<Integer> 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(); |
| localLog("Successfully stopped all requests for client " + this); |
| } |
| } |
| |
| void replySucceeded(Message msg) { |
| if (msg.replyTo != null) { |
| Message reply = Message.obtain(); |
| reply.what = WifiScanner.CMD_OP_SUCCEEDED; |
| reply.arg2 = msg.arg2; |
| 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 class SettingsComputer { |
| |
| private class TimeBucket { |
| int periodInSecond; |
| int periodMinInSecond; |
| int periodMaxInSecond; |
| |
| TimeBucket(int p, int min, int max) { |
| periodInSecond = p; |
| periodMinInSecond = min; |
| periodMaxInSecond = max; |
| } |
| } |
| |
| private 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, 240), |
| new TimeBucket( 300, 240, 500), |
| new TimeBucket( 600, 500, 1500), |
| new TimeBucket( 1800, 1500, WifiScanner.MAX_SCAN_PERIOD_MS) }; |
| |
| private static final int MAX_CHANNELS = 32; |
| private static final int DEFAULT_BASE_PERIOD_MS = 5000; |
| private static final int DEFAULT_REPORT_THRESHOLD_NUM_SCANS = 10; |
| private static final int DEFAULT_REPORT_THRESHOLD_PERCENT = 100; |
| |
| private WifiNative.ScanSettings mSettings; |
| { |
| mSettings = new WifiNative.ScanSettings(); |
| mSettings.max_ap_per_scan = mScanCapabilities.max_ap_cache_per_scan; |
| mSettings.base_period_ms = DEFAULT_BASE_PERIOD_MS; |
| mSettings.report_threshold_percent = DEFAULT_REPORT_THRESHOLD_PERCENT; |
| mSettings.report_threshold_num_scans = DEFAULT_REPORT_THRESHOLD_NUM_SCANS; |
| |
| mSettings.buckets = new WifiNative.BucketSettings[mScanCapabilities.max_scan_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; |
| mSettings.buckets[i] = bucketSettings; |
| } |
| } |
| |
| HashMap<Integer, Integer> mChannelToBucketMap = new HashMap<Integer, Integer>(); |
| |
| private int getBestBucket(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) |
| |
| 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 |
| loge("No channels to scan!!"); |
| return -1; |
| } |
| |
| int mostFrequentBucketIndex = mTimeBuckets.length; |
| |
| for (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 (ChannelSpec desiredChannelSpec : channels) { |
| mChannelToBucketMap.put(desiredChannelSpec.frequency, mostFrequentBucketIndex); |
| } |
| localLog("returning mf bucket number " + mostFrequentBucketIndex); |
| return mostFrequentBucketIndex; |
| } else if (bestBucketIndex != -1) { |
| for (ChannelSpec desiredChannelSpec : channels) { |
| mChannelToBucketMap.put(desiredChannelSpec.frequency, bestBucketIndex); |
| } |
| localLog("returning best bucket number " + bestBucketIndex); |
| return bestBucketIndex; |
| } |
| |
| loge("Could not find suitable bucket for period " + settings.periodInMs); |
| return -1; |
| } |
| |
| void prepChannelMap(ScanSettings settings) { |
| getBestBucket(settings); |
| } |
| |
| int addScanRequestToBucket(ScanSettings settings) { |
| |
| int bucketIndex = getBestBucket(settings); |
| if (bucketIndex == -1) { |
| loge("Ignoring invalid settings"); |
| return -1; |
| } |
| |
| 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 |
| loge("No channels to scan!!"); |
| return -1; |
| } |
| } |
| |
| // merge the channel lists for these buckets |
| localLog("merging " + desiredChannels.length + " channels " |
| + " for period " + settings.periodInMs |
| + " maxScans " + settings.maxScansToCache); |
| |
| WifiNative.BucketSettings bucket = mSettings.buckets[bucketIndex]; |
| boolean added = (bucket.num_channels == 0) |
| && (bucket.band == WifiScanner.WIFI_BAND_UNSPECIFIED); |
| localLog("existing " + bucket.num_channels + " channels "); |
| |
| HashSet<ChannelSpec> newChannels = new HashSet<ChannelSpec>(); |
| for (ChannelSpec desiredChannelSpec : desiredChannels) { |
| |
| if (DBG) localLog("desired channel " + desiredChannelSpec.frequency); |
| |
| boolean found = false; |
| for (int i = 0; i < bucket.num_channels; i++) { |
| if (desiredChannelSpec.frequency == bucket.channels[i].frequency) { |
| found = true; |
| break; |
| } |
| } |
| |
| if (!found) { |
| newChannels.add(desiredChannelSpec); |
| } else { |
| if (DBG) localLog("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]; |
| localLog("switching to using band " + bucket.band); |
| } else { |
| for (ChannelSpec desiredChannelSpec : newChannels) { |
| |
| localLog("adding new channel spec " + desiredChannelSpec.frequency); |
| |
| WifiNative.ChannelSettings channelSettings = new WifiNative.ChannelSettings(); |
| channelSettings.frequency = desiredChannelSpec.frequency; |
| bucket.channels[bucket.num_channels] = channelSettings; |
| bucket.num_channels++; |
| mChannelToBucketMap.put(bucketIndex, channelSettings.frequency); |
| } |
| } |
| |
| if (bucket.report_events < settings.reportEvents) { |
| if (DBG) localLog("setting report_events to " + settings.reportEvents); |
| bucket.report_events = settings.reportEvents; |
| } else { |
| if (DBG) localLog("report_events is " + settings.reportEvents); |
| } |
| |
| if (added) { |
| bucket.period_ms = mTimeBuckets[bucketIndex].periodInSecond * 1000; |
| mSettings.num_buckets++; |
| } |
| |
| if ( settings.numBssidsPerScan != 0) { |
| if (mSettings.max_ap_per_scan > settings.numBssidsPerScan) { |
| mSettings.max_ap_per_scan = settings.numBssidsPerScan; |
| } |
| } |
| |
| if (settings.maxScansToCache != 0) { |
| if (mSettings.report_threshold_num_scans > settings.maxScansToCache) { |
| mSettings.report_threshold_num_scans = settings.maxScansToCache; |
| } |
| } |
| |
| 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<ClientInfo> clients = mClients.values(); |
| for (ClientInfo ci : clients) { |
| Collection<ScanSettings> settings = ci.getScanSettings(); |
| for (ScanSettings s : settings) { |
| c.prepChannelMap(s); |
| } |
| } |
| |
| for (ClientInfo ci : clients) { |
| Iterator it = ci.getScans(); |
| while (it.hasNext()) { |
| Map.Entry<Integer, ScanSettings> entry = |
| (Map.Entry<Integer,ScanSettings>)it.next(); |
| int id = entry.getKey(); |
| ScanSettings s = entry.getValue(); |
| int newPeriodInMs = c.addScanRequestToBucket(s); |
| if (newPeriodInMs == -1) { |
| if (DBG) localLog("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) localLog("Stopping scan because there are no buckets"); |
| WifiNative.stopScan(); |
| return true; |
| } else { |
| if (WifiNative.startScan(s, mStateMachine)) { |
| localLog("Successfully started scan of " + s.num_buckets + " buckets at" |
| + "time = " + SystemClock.elapsedRealtimeNanos() / 1000 + " period " |
| + s.base_period_ms); |
| return true; |
| } else { |
| loge("Failed to start scan of " + s.num_buckets + " buckets"); |
| return false; |
| } |
| } |
| } |
| |
| void logScanRequest(String request, ClientInfo ci, int id, ScanSettings settings) { |
| StringBuffer sb = new StringBuffer(); |
| sb.append(request); |
| sb.append("\nClient "); |
| sb.append(ci.toString()); |
| sb.append("\nId "); |
| sb.append(id); |
| sb.append("\n"); |
| if (settings != null) { |
| sb.append(describe(settings)); |
| sb.append("\n"); |
| } |
| sb.append("\n"); |
| localLog(sb.toString()); |
| } |
| |
| boolean addScanRequest(ClientInfo ci, int handler, ScanSettings settings) { |
| // sanity check the input |
| if (ci == null) { |
| Log.d(TAG, "Failing scan request ClientInfo not found " + handler); |
| return false; |
| } |
| if (settings.periodInMs < WifiScanner.MIN_SCAN_PERIOD_MS) { |
| localLog("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) { |
| localLog("Failing scan request because minSupportedPeriodMs is " |
| + minSupportedPeriodMs + " but the request wants " + settings.periodInMs); |
| return false; |
| } |
| |
| logScanRequest("addScanRequest", ci, handler, settings); |
| removeAllScanRequests(); |
| ci.addScanRequest(settings, handler); |
| if (resetBuckets()) { |
| return true; |
| } else { |
| ci.removeScanRequest(handler); |
| localLog("Failing scan request because failed to reset scan"); |
| return false; |
| } |
| } |
| |
| boolean addSingleScanRequest(ClientInfo ci, int handler, ScanSettings settings) { |
| if (ci == null) { |
| Log.d(TAG, "Failing single scan request ClientInfo not found " + handler); |
| return false; |
| } |
| if (settings.reportEvents == 0) { |
| settings.reportEvents = WifiScanner.REPORT_EVENT_AFTER_EACH_SCAN; |
| } |
| if (settings.periodInMs == 0) { |
| settings.periodInMs = 10000; // 10s - although second scan should never happen |
| } |
| |
| logScanRequest("addSingleScanRequest", ci, handler, settings); |
| removeAllScanRequests(); |
| ci.addScanRequest(settings, handler); |
| if (resetBuckets()) { |
| /* reset periodInMs to 0 to indicate single shot scan */ |
| settings.periodInMs = 0; |
| return true; |
| } else { |
| ci.removeScanRequest(handler); |
| localLog("Failing scan request because failed to reset scan"); |
| return false; |
| } |
| } |
| |
| void removeScanRequest(ClientInfo ci, int handler) { |
| if (ci != null) { |
| logScanRequest("removeScanRequest", ci, handler, null); |
| ci.removeScanRequest(handler); |
| resetBuckets(); |
| } |
| } |
| |
| void removeAllScanRequests() { |
| Collection<ClientInfo> clients = mClients.values(); |
| for (ClientInfo ci : clients) { |
| ci.removeAllScanRequests(); |
| } |
| } |
| |
| boolean reportScanResults() { |
| ScanData results[] = WifiNative.getScanResults(/* flush = */ true); |
| Collection<ClientInfo> clients = mClients.values(); |
| for (ClientInfo ci2 : clients) { |
| ci2.reportScanResults(results); |
| } |
| |
| return true; |
| } |
| |
| void resetHotlist() { |
| Collection<ClientInfo> clients = mClients.values(); |
| int num_hotlist_ap = 0; |
| |
| for (ClientInfo ci : clients) { |
| Collection<WifiScanner.HotlistSettings> c = ci.getHotlistSettings(); |
| for (WifiScanner.HotlistSettings s : c) { |
| num_hotlist_ap += s.bssidInfos.length; |
| } |
| } |
| |
| if (num_hotlist_ap == 0) { |
| WifiNative.resetHotlist(); |
| } else { |
| BssidInfo bssidInfos[] = new BssidInfo[num_hotlist_ap]; |
| int index = 0; |
| for (ClientInfo ci : clients) { |
| Collection<WifiScanner.HotlistSettings> 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<ClientInfo> 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<ClientInfo> clients = mClients.values(); |
| for (ClientInfo ci : clients) { |
| ci.reportWifiChanged(results); |
| } |
| } |
| |
| void reportWifiStabilized(ScanResult results[]) { |
| Collection<ClientInfo> 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) localLog("Entering IdleState"); |
| } |
| |
| @Override |
| public boolean processMessage(Message msg) { |
| if (DBG) localLog("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) localLog("Entering StationaryState"); |
| reportWifiStabilized(mCurrentBssids); |
| } |
| |
| @Override |
| public boolean processMessage(Message msg) { |
| if (DBG) localLog("Stationary state got " + msg); |
| switch (msg.what) { |
| case WIFI_CHANGE_CMD_ENABLE : |
| // do nothing |
| break; |
| case WIFI_CHANGE_CMD_CHANGE_DETECTED: |
| if (DBG) localLog("Got wifi change detected"); |
| reportWifiChanged((ScanResult[])msg.obj); |
| transitionTo(mMovingState); |
| break; |
| case WIFI_CHANGE_CMD_DISABLE: |
| if (DBG) localLog("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) localLog("Entering MovingState"); |
| issueFullScan(); |
| } |
| |
| @Override |
| public boolean processMessage(Message msg) { |
| if (DBG) localLog("MovingState state got " + msg); |
| switch (msg.what) { |
| case WIFI_CHANGE_CMD_ENABLE : |
| // do nothing |
| break; |
| case WIFI_CHANGE_CMD_DISABLE: |
| if (DBG) localLog("Got Disable Wifi Change"); |
| mCurrentBssids = null; |
| removeScanRequest(); |
| untrackSignificantWifiChange(); |
| transitionTo(mDefaultState); |
| break; |
| case WIFI_CHANGE_CMD_NEW_SCAN_RESULTS: |
| if (DBG) localLog("Got scan results"); |
| if (mScanResultsPending) { |
| if (DBG) localLog("reconfiguring scan"); |
| reconfigureScan((ScanData[])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) localLog("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) localLog("Change detected"); |
| mAlarmManager.cancel(mTimeoutIntent); |
| reportWifiChanged((ScanResult[])msg.obj); |
| mWifiChangeDetected = true; |
| issueFullScan(); |
| break; |
| case WIFI_CHANGE_CMD_CHANGE_TIMEOUT: |
| if (DBG) localLog("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) localLog("Issuing full scan"); |
| ScanSettings settings = new 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(ScanData[] results, int period) { |
| // find brightest APs and set them as sentinels |
| if (results.length < MAX_APS_TO_TRACK) { |
| localLog("too few APs (" + results.length + ") available to track wifi change"); |
| return; |
| } |
| |
| removeScanRequest(); |
| |
| // remove duplicate BSSIDs |
| HashMap<String, ScanResult> bssidToScanResult = new HashMap<String, ScanResult>(); |
| for (ScanResult result : results[0].getResults()) { |
| 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<ScanResult> 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<Integer> channels = new ArrayList<Integer>(); |
| 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) localLog("Found " + channels.size() + " channels"); |
| |
| // set scanning schedule |
| ScanSettings settings = new ScanSettings(); |
| settings.band = WifiScanner.WIFI_BAND_UNSPECIFIED; |
| settings.channels = new ChannelSpec[channels.size()]; |
| for (int i = 0; i < channels.size(); i++) { |
| settings.channels[i] = new 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 BssidInfo[brightest.length]; |
| |
| for (int i = 0; i < brightest.length; i++) { |
| BssidInfo BssidInfo = new 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) localLog("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) { |
| localLog("too few APs (" + settings.bssidInfos.length |
| + ") available to track wifi change"); |
| return; |
| } |
| |
| if (DBG) localLog("Setting configuration specified by app"); |
| |
| mCurrentBssids = new ScanResult[settings.bssidInfos.length]; |
| HashSet<Integer> channels = new HashSet<Integer>(); |
| |
| 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 |
| ScanSettings settings2 = new ScanSettings(); |
| settings2.band = WifiScanner.WIFI_BAND_UNSPECIFIED; |
| settings2.channels = new ChannelSpec[channels.size()]; |
| int i = 0; |
| for (Integer channel : channels) { |
| settings2.channels[i++] = new ChannelSpec(channel); |
| } |
| |
| settings2.periodInMs = settings.periodInMs; |
| addScanRequest(settings2); |
| |
| // start tracking new APs |
| trackSignificantWifiChange(settings); |
| } |
| |
| class ClientInfoLocal extends ClientInfo { |
| ClientInfoLocal() { |
| super(0, null, null); |
| } |
| @Override |
| void deliverScanResults(int handler, ScanData results[]) { |
| if (DBG) localLog("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(ScanSettings settings) { |
| if (DBG) localLog("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) localLog("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 ChannelSpec mChannels[][]; |
| |
| private static void copyChannels( |
| ChannelSpec channelSpec[], int offset, int channels[]) { |
| for (int i = 0; i < channels.length; i++) { |
| channelSpec[offset +i] = new ChannelSpec(channels[i]); |
| } |
| } |
| |
| private static boolean initChannels() { |
| if (mChannels != null) { |
| /* already initialized */ |
| return true; |
| } |
| |
| int channels24[] = WifiNative.getChannelsForBand(WifiScanner.WIFI_BAND_24_GHZ); |
| if (channels24 == null) { |
| loge("Could not get channels for 2.4 GHz"); |
| return false; |
| } |
| |
| int channels5[] = WifiNative.getChannelsForBand(WifiScanner.WIFI_BAND_5_GHZ); |
| if (channels5 == null) { |
| loge("Could not get channels for 5 GHz"); |
| return false; |
| } |
| |
| int channelsDfs[] = WifiNative.getChannelsForBand(WifiScanner.WIFI_BAND_5_GHZ_DFS_ONLY); |
| if (channelsDfs == null) { |
| loge("Could not get channels for DFS"); |
| return false; |
| } |
| |
| mChannels = new ChannelSpec[8][]; |
| |
| mChannels[0] = new ChannelSpec[0]; |
| |
| mChannels[1] = new ChannelSpec[channels24.length]; |
| copyChannels(mChannels[1], 0, channels24); |
| |
| mChannels[2] = new ChannelSpec[channels5.length]; |
| copyChannels(mChannels[2], 0, channels5); |
| |
| mChannels[3] = new ChannelSpec[channels24.length + channels5.length]; |
| copyChannels(mChannels[3], 0, channels24); |
| copyChannels(mChannels[3], channels24.length, channels5); |
| |
| mChannels[4] = new ChannelSpec[channelsDfs.length]; |
| copyChannels(mChannels[4], 0, channelsDfs); |
| |
| mChannels[5] = new ChannelSpec[channels24.length + channelsDfs.length]; |
| copyChannels(mChannels[5], 0, channels24); |
| copyChannels(mChannels[5], channels24.length, channelsDfs); |
| |
| mChannels[6] = new ChannelSpec[channels5.length + channelsDfs.length]; |
| copyChannels(mChannels[6], 0, channels5); |
| copyChannels(mChannels[6], channels5.length, channelsDfs); |
| |
| mChannels[7] = new ChannelSpec[ |
| channels24.length + channels5.length + channelsDfs.length]; |
| copyChannels(mChannels[7], 0, channels24); |
| copyChannels(mChannels[7], channels24.length, channels5); |
| copyChannels(mChannels[7], channels24.length + channels5.length, channelsDfs); |
| |
| return true; |
| } |
| |
| private static ChannelSpec[] getChannelsForBand(int band) { |
| initChannels(); |
| |
| if (band < WifiScanner.WIFI_BAND_24_GHZ || band > WifiScanner.WIFI_BAND_BOTH_WITH_DFS) |
| /* invalid value for band */ |
| return mChannels[0]; |
| else |
| return mChannels[band]; |
| } |
| |
| private static boolean isDfs(int channel) { |
| ChannelSpec[] dfsChannels = getChannelsForBand(WifiScanner |
| .WIFI_BAND_5_GHZ_DFS_ONLY); |
| for (int i = 0; i < dfsChannels.length; i++) { |
| if (channel == dfsChannels[i].frequency) { |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| private static int getBandFromChannels(ChannelSpec[] channels) { |
| int band = WifiScanner.WIFI_BAND_UNSPECIFIED; |
| for (ChannelSpec channel : channels) { |
| if (2400 <= channel.frequency && channel.frequency < 2500) { |
| band |= WifiScanner.WIFI_BAND_24_GHZ; |
| } else if ( isDfs(channel.frequency)) { |
| band |= WifiScanner.WIFI_BAND_5_GHZ_DFS_ONLY; |
| } else if (5100 <= channel.frequency && channel.frequency < 6000) { |
| band |= WifiScanner.WIFI_BAND_5_GHZ; |
| } |
| } |
| return band; |
| } |
| |
| private static int getBandFromChannels(WifiNative.ChannelSettings[] channels) { |
| int band = WifiScanner.WIFI_BAND_UNSPECIFIED; |
| for (WifiNative.ChannelSettings channel : channels) { |
| if (channel != null) { |
| if (2400 <= channel.frequency && channel.frequency < 2500) { |
| band |= WifiScanner.WIFI_BAND_24_GHZ; |
| } else if ( isDfs(channel.frequency)) { |
| band |= WifiScanner.WIFI_BAND_5_GHZ_DFS_ONLY; |
| } else if (5100 <= channel.frequency && channel.frequency < 6000) { |
| band |= WifiScanner.WIFI_BAND_5_GHZ; |
| } |
| } |
| } |
| return band; |
| } |
| |
| @Override |
| protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) { |
| if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.DUMP) |
| != PackageManager.PERMISSION_GRANTED) { |
| pw.println("Permission Denial: can't dump WifiScanner from from pid=" |
| + Binder.getCallingPid() |
| + ", uid=" + Binder.getCallingUid() |
| + " without permission " |
| + android.Manifest.permission.DUMP); |
| return; |
| } |
| mStateMachine.dump(fd, pw, args); |
| } |
| |
| static String describe(ScanSettings scanSettings) { |
| StringBuilder sb = new StringBuilder(); |
| sb.append(" band:").append(scanSettings.band); |
| sb.append(" period:").append(scanSettings.periodInMs); |
| sb.append(" reportEvents:").append(scanSettings.reportEvents); |
| sb.append(" numBssidsPerScan:").append(scanSettings.numBssidsPerScan); |
| sb.append(" maxScansToCache:").append(scanSettings.maxScansToCache).append("\n"); |
| |
| sb.append(" channels: "); |
| |
| if (scanSettings.channels != null) { |
| for (int i = 0; i < scanSettings.channels.length; i++) { |
| sb.append(scanSettings.channels[i].frequency); |
| sb.append(" "); |
| } |
| } |
| sb.append("\n"); |
| return sb.toString(); |
| } |
| |
| } |