/* * 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.scanner; 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.PnoSettings; import android.net.wifi.WifiScanner.ScanData; import android.net.wifi.WifiScanner.ScanSettings; import android.os.Binder; import android.os.Bundle; import android.os.Handler; import android.os.Looper; import android.os.Message; import android.os.Messenger; import android.os.RemoteException; import android.os.WorkSource; import android.util.ArrayMap; import android.util.LocalLog; import android.util.Log; import android.util.Pair; import com.android.internal.app.IBatteryStats; import com.android.internal.util.AsyncChannel; import com.android.internal.util.Protocol; import com.android.internal.util.State; import com.android.internal.util.StateMachine; import com.android.server.wifi.Clock; import com.android.server.wifi.WifiInjector; import com.android.server.wifi.WifiMetrics; import com.android.server.wifi.WifiMetricsProto; import com.android.server.wifi.WifiNative; import com.android.server.wifi.WifiStateMachine; import com.android.server.wifi.scanner.ChannelHelper.ChannelCollection; 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.Set; public class WifiScanningServiceImpl extends IWifiScanner.Stub { private static final String TAG = WifiScanningService.TAG; private static final boolean DBG = false; private static final int MIN_PERIOD_PER_CHANNEL_MS = 200; // DFS needs 120 ms private static final int UNKNOWN_PID = -1; private final LocalLog mLocalLog = new LocalLog(512); private void localLog(String message) { mLocalLog.log(message); } private void logw(String message) { Log.w(TAG, message); mLocalLog.log(message); } private void loge(String message) { Log.e(TAG, message); mLocalLog.log(message); } private WifiScannerImpl mScannerImpl; @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) { mChannelHelper.updateChannels(); ChannelSpec[] channelSpecs = mChannelHelper.getAvailableScanChannels(band); ArrayList list = new ArrayList(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(Looper looper) { super(looper); } @Override public void handleMessage(Message msg) { switch (msg.what) { case AsyncChannel.CMD_CHANNEL_FULL_CONNECTION: { ExternalClientInfo client = (ExternalClientInfo) mClients.get(msg.replyTo); if (client != null) { logw("duplicate client connection: " + msg.sendingUid + ", messenger=" + msg.replyTo); client.mChannel.replyToMessage(msg, AsyncChannel.CMD_CHANNEL_FULLY_CONNECTED, AsyncChannel.STATUS_FULL_CONNECTION_REFUSED_ALREADY_CONNECTED); return; } AsyncChannel ac = new AsyncChannel(); ac.connected(mContext, this, msg.replyTo); client = new ExternalClientInfo(msg.sendingUid, msg.replyTo, ac); client.register(); ac.replyToMessage(msg, AsyncChannel.CMD_CHANNEL_FULLY_CONNECTED, AsyncChannel.STATUS_SUCCESSFUL); localLog("client connected: " + client); return; } case AsyncChannel.CMD_CHANNEL_DISCONNECT: { ExternalClientInfo client = (ExternalClientInfo) mClients.get(msg.replyTo); if (client != null) { client.mChannel.disconnect(); } return; } case AsyncChannel.CMD_CHANNEL_DISCONNECTED: { ExternalClientInfo client = (ExternalClientInfo) mClients.get(msg.replyTo); if (client != null && msg.arg1 != AsyncChannel.STATUS_SEND_UNSUCCESSFUL && msg.arg1 != AsyncChannel.STATUS_FULL_CONNECTION_REFUSED_ALREADY_CONNECTED) { localLog("client disconnected: " + client + ", reason: " + msg.arg1); client.cleanup(); } return; } } try { enforceLocationHardwarePermission(msg.sendingUid); } catch (SecurityException e) { localLog("failed to authorize app: " + e); replyFailed(msg, WifiScanner.REASON_NOT_AUTHORIZED, "Not authorized"); return; } // Since this message is sent from WifiScanner using |sendMessageSynchronously| which // doesn't set the correct |msg.replyTo| field. if (msg.what == WifiScanner.CMD_GET_SCAN_RESULTS) { mBackgroundScanStateMachine.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; } switch (msg.what) { case WifiScanner.CMD_START_BACKGROUND_SCAN: case WifiScanner.CMD_STOP_BACKGROUND_SCAN: case WifiScanner.CMD_SET_HOTLIST: case WifiScanner.CMD_RESET_HOTLIST: mBackgroundScanStateMachine.sendMessage(Message.obtain(msg)); break; case WifiScanner.CMD_START_PNO_SCAN: case WifiScanner.CMD_STOP_PNO_SCAN: mPnoScanStateMachine.sendMessage(Message.obtain(msg)); break; case WifiScanner.CMD_START_SINGLE_SCAN: case WifiScanner.CMD_STOP_SINGLE_SCAN: mSingleScanStateMachine.sendMessage(Message.obtain(msg)); break; case WifiScanner.CMD_CONFIGURE_WIFI_CHANGE: case WifiScanner.CMD_START_TRACKING_CHANGE: case WifiScanner.CMD_STOP_TRACKING_CHANGE: mWifiChangeStateMachine.sendMessage(Message.obtain(msg)); break; case WifiScanner.CMD_REGISTER_SCAN_LISTENER: logScanRequest("registerScanListener", ci, msg.arg2, null, null, null); mSingleScanListeners.addRequest(ci, msg.arg2, null, null); replySucceeded(msg); break; case WifiScanner.CMD_DEREGISTER_SCAN_LISTENER: logScanRequest("deregisterScanListener", ci, msg.arg2, null, null, null); mSingleScanListeners.removeRequest(ci, msg.arg2); break; default: replyFailed(msg, WifiScanner.REASON_INVALID_REQUEST, "Invalid request"); break; } } } 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_CHANGE_TIMEOUT = 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_SCAN_FAILED = BASE + 10; private static final int CMD_PNO_NETWORK_FOUND = BASE + 11; private static final int CMD_PNO_SCAN_FAILED = BASE + 12; private final Context mContext; private final Looper mLooper; private final WifiScannerImpl.WifiScannerImplFactory mScannerImplFactory; private final ArrayMap mClients; private final RequestList mSingleScanListeners = new RequestList<>(); private ChannelHelper mChannelHelper; private BackgroundScanScheduler mBackgroundScheduler; private WifiNative.ScanSettings mPreviousSchedule; private WifiBackgroundScanStateMachine mBackgroundScanStateMachine; private WifiSingleScanStateMachine mSingleScanStateMachine; private WifiChangeStateMachine mWifiChangeStateMachine; private WifiPnoScanStateMachine mPnoScanStateMachine; private ClientHandler mClientHandler; private final IBatteryStats mBatteryStats; private final AlarmManager mAlarmManager; private final WifiMetrics mWifiMetrics; private final Clock mClock; WifiScanningServiceImpl(Context context, Looper looper, WifiScannerImpl.WifiScannerImplFactory scannerImplFactory, IBatteryStats batteryStats, WifiInjector wifiInjector) { mContext = context; mLooper = looper; mScannerImplFactory = scannerImplFactory; mBatteryStats = batteryStats; mClients = new ArrayMap<>(); mAlarmManager = (AlarmManager) mContext.getSystemService(Context.ALARM_SERVICE); mWifiMetrics = wifiInjector.getWifiMetrics(); mClock = wifiInjector.getClock(); mPreviousSchedule = null; } public void startService() { mClientHandler = new ClientHandler(mLooper); mBackgroundScanStateMachine = new WifiBackgroundScanStateMachine(mLooper); mWifiChangeStateMachine = new WifiChangeStateMachine(mLooper); mSingleScanStateMachine = new WifiSingleScanStateMachine(mLooper); mPnoScanStateMachine = new WifiPnoScanStateMachine(mLooper); 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) { mBackgroundScanStateMachine.sendMessage(CMD_DRIVER_LOADED); mSingleScanStateMachine.sendMessage(CMD_DRIVER_LOADED); mPnoScanStateMachine.sendMessage(CMD_DRIVER_LOADED); } else if (state == WifiManager.WIFI_STATE_DISABLED) { mBackgroundScanStateMachine.sendMessage(CMD_DRIVER_UNLOADED); mSingleScanStateMachine.sendMessage(CMD_DRIVER_UNLOADED); mPnoScanStateMachine.sendMessage(CMD_DRIVER_UNLOADED); } } }, new IntentFilter(WifiManager.WIFI_SCAN_AVAILABLE)); mBackgroundScanStateMachine.start(); mWifiChangeStateMachine.start(); mSingleScanStateMachine.start(); mPnoScanStateMachine.start(); } private static boolean isWorkSourceValid(WorkSource workSource) { return workSource != null && workSource.size() > 0 && workSource.get(0) >= 0; } private WorkSource computeWorkSource(ClientInfo ci, WorkSource requestedWorkSource) { if (requestedWorkSource != null) { if (isWorkSourceValid(requestedWorkSource)) { // Wifi currently doesn't use names, so need to clear names out of the // supplied WorkSource to allow future WorkSource combining. requestedWorkSource.clearNames(); return requestedWorkSource; } else { loge("Got invalid work source request: " + requestedWorkSource.toString() + " from " + ci); } } WorkSource callingWorkSource = new WorkSource(ci.getUid()); if (isWorkSourceValid(callingWorkSource)) { return callingWorkSource; } else { loge("Client has invalid work source: " + callingWorkSource); return new WorkSource(); } } private class RequestInfo { final ClientInfo clientInfo; final int handlerId; final WorkSource workSource; final T settings; RequestInfo(ClientInfo clientInfo, int handlerId, WorkSource requestedWorkSource, T settings) { this.clientInfo = clientInfo; this.handlerId = handlerId; this.settings = settings; this.workSource = computeWorkSource(clientInfo, requestedWorkSource); } void reportEvent(int what, int arg1, Object obj) { clientInfo.reportEvent(what, arg1, handlerId, obj); } } private class RequestList extends ArrayList> { void addRequest(ClientInfo ci, int handler, WorkSource reqworkSource, T settings) { add(new RequestInfo(ci, handler, reqworkSource, settings)); } T removeRequest(ClientInfo ci, int handlerId) { T removed = null; Iterator> iter = iterator(); while (iter.hasNext()) { RequestInfo entry = iter.next(); if (entry.clientInfo == ci && entry.handlerId == handlerId) { removed = entry.settings; iter.remove(); } } return removed; } Collection getAllSettings() { ArrayList settingsList = new ArrayList<>(); Iterator> iter = iterator(); while (iter.hasNext()) { RequestInfo entry = iter.next(); settingsList.add(entry.settings); } return settingsList; } Collection getAllSettingsForClient(ClientInfo ci) { ArrayList settingsList = new ArrayList<>(); Iterator> iter = iterator(); while (iter.hasNext()) { RequestInfo entry = iter.next(); if (entry.clientInfo == ci) { settingsList.add(entry.settings); } } return settingsList; } void removeAllForClient(ClientInfo ci) { Iterator> iter = iterator(); while (iter.hasNext()) { RequestInfo entry = iter.next(); if (entry.clientInfo == ci) { iter.remove(); } } } WorkSource createMergedWorkSource() { WorkSource mergedSource = new WorkSource(); for (RequestInfo entry : this) { mergedSource.add(entry.workSource); } return mergedSource; } } /** * State machine that holds the state of single scans. Scans should only be active in the * ScanningState. The pending scans and active scans maps are swaped when entering * ScanningState. Any requests queued while scanning will be placed in the pending queue and * executed after transitioning back to IdleState. */ class WifiSingleScanStateMachine extends StateMachine implements WifiNative.ScanEventHandler { private final DefaultState mDefaultState = new DefaultState(); private final DriverStartedState mDriverStartedState = new DriverStartedState(); private final IdleState mIdleState = new IdleState(); private final ScanningState mScanningState = new ScanningState(); private WifiNative.ScanSettings mActiveScanSettings = null; private RequestList mActiveScans = new RequestList<>(); private RequestList mPendingScans = new RequestList<>(); WifiSingleScanStateMachine(Looper looper) { super("WifiSingleScanStateMachine", looper); setLogRecSize(128); setLogOnlyTransitions(false); // CHECKSTYLE:OFF IndentationCheck addState(mDefaultState); addState(mDriverStartedState, mDefaultState); addState(mIdleState, mDriverStartedState); addState(mScanningState, mDriverStartedState); // CHECKSTYLE:ON IndentationCheck setInitialState(mDefaultState); } /** * Called to indicate a change in state for the current scan. * Will dispatch a coresponding event to the state machine */ @Override public void onScanStatus(int event) { if (DBG) localLog("onScanStatus event received, event=" + event); switch(event) { case WifiNative.WIFI_SCAN_RESULTS_AVAILABLE: case WifiNative.WIFI_SCAN_THRESHOLD_NUM_SCANS: case WifiNative.WIFI_SCAN_THRESHOLD_PERCENT: sendMessage(CMD_SCAN_RESULTS_AVAILABLE); break; case WifiNative.WIFI_SCAN_FAILED: sendMessage(CMD_SCAN_FAILED); break; default: Log.e(TAG, "Unknown scan status event: " + event); break; } } /** * Called for each full scan result if requested */ @Override public void onFullScanResult(ScanResult fullScanResult, int bucketsScanned) { if (DBG) localLog("onFullScanResult received"); sendMessage(CMD_FULL_SCAN_RESULTS, 0, bucketsScanned, fullScanResult); } @Override public void onScanPaused(ScanData[] scanData) { // should not happen for single scan Log.e(TAG, "Got scan paused for single scan"); } @Override public void onScanRestarted() { // should not happen for single scan Log.e(TAG, "Got scan restarted for single scan"); } class DefaultState extends State { @Override public void enter() { mActiveScans.clear(); mPendingScans.clear(); } @Override public boolean processMessage(Message msg) { switch (msg.what) { case CMD_DRIVER_LOADED: transitionTo(mIdleState); return HANDLED; case CMD_DRIVER_UNLOADED: transitionTo(mDefaultState); return HANDLED; case WifiScanner.CMD_START_SINGLE_SCAN: case WifiScanner.CMD_STOP_SINGLE_SCAN: replyFailed(msg, WifiScanner.REASON_UNSPECIFIED, "not available"); return HANDLED; case CMD_SCAN_RESULTS_AVAILABLE: if (DBG) localLog("ignored scan results available event"); return HANDLED; case CMD_FULL_SCAN_RESULTS: if (DBG) localLog("ignored full scan result event"); return HANDLED; default: return NOT_HANDLED; } } } /** * State representing when the driver is running. This state is not meant to be transitioned * directly, but is instead indented as a parent state of ScanningState and IdleState * to hold common functionality and handle cleaning up scans when the driver is shut down. */ class DriverStartedState extends State { @Override public void exit() { mWifiMetrics.incrementScanReturnEntry( WifiMetricsProto.WifiLog.SCAN_FAILURE_INTERRUPTED, mPendingScans.size()); sendOpFailedToAllAndClear(mPendingScans, WifiScanner.REASON_UNSPECIFIED, "Scan was interrupted"); } @Override public boolean processMessage(Message msg) { ClientInfo ci = mClients.get(msg.replyTo); switch (msg.what) { case WifiScanner.CMD_START_SINGLE_SCAN: mWifiMetrics.incrementOneshotScanCount(); int handler = msg.arg2; Bundle scanParams = (Bundle) msg.obj; if (scanParams == null) { logCallback("singleScanInvalidRequest", ci, handler, "null params"); replyFailed(msg, WifiScanner.REASON_INVALID_REQUEST, "params null"); return HANDLED; } scanParams.setDefusable(true); ScanSettings scanSettings = scanParams.getParcelable(WifiScanner.SCAN_PARAMS_SCAN_SETTINGS_KEY); WorkSource workSource = scanParams.getParcelable(WifiScanner.SCAN_PARAMS_WORK_SOURCE_KEY); if (validateScanRequest(ci, handler, scanSettings, workSource)) { logScanRequest("addSingleScanRequest", ci, handler, workSource, scanSettings, null); replySucceeded(msg); // If there is an active scan that will fulfill the scan request then // mark this request as an active scan, otherwise mark it pending. // If were not currently scanning then try to start a scan. Otherwise // this scan will be scheduled when transitioning back to IdleState // after finishing the current scan. if (getCurrentState() == mScanningState) { if (activeScanSatisfies(scanSettings)) { mActiveScans.addRequest(ci, handler, workSource, scanSettings); } else { mPendingScans.addRequest(ci, handler, workSource, scanSettings); } } else { mPendingScans.addRequest(ci, handler, workSource, scanSettings); tryToStartNewScan(); } } else { logCallback("singleScanInvalidRequest", ci, handler, "bad request"); replyFailed(msg, WifiScanner.REASON_INVALID_REQUEST, "bad request"); mWifiMetrics.incrementScanReturnEntry( WifiMetricsProto.WifiLog.SCAN_FAILURE_INVALID_CONFIGURATION, 1); } return HANDLED; case WifiScanner.CMD_STOP_SINGLE_SCAN: removeSingleScanRequest(ci, msg.arg2); return HANDLED; default: return NOT_HANDLED; } } } class IdleState extends State { @Override public void enter() { tryToStartNewScan(); } @Override public boolean processMessage(Message msg) { return NOT_HANDLED; } } class ScanningState extends State { private WorkSource mScanWorkSource; @Override public void enter() { mScanWorkSource = mActiveScans.createMergedWorkSource(); try { mBatteryStats.noteWifiScanStartedFromSource(mScanWorkSource); } catch (RemoteException e) { loge(e.toString()); } } @Override public void exit() { mActiveScanSettings = null; try { mBatteryStats.noteWifiScanStoppedFromSource(mScanWorkSource); } catch (RemoteException e) { loge(e.toString()); } // if any scans are still active (never got results available then indicate failure) mWifiMetrics.incrementScanReturnEntry( WifiMetricsProto.WifiLog.SCAN_UNKNOWN, mActiveScans.size()); sendOpFailedToAllAndClear(mActiveScans, WifiScanner.REASON_UNSPECIFIED, "Scan was interrupted"); } @Override public boolean processMessage(Message msg) { switch (msg.what) { case CMD_SCAN_RESULTS_AVAILABLE: mWifiMetrics.incrementScanReturnEntry( WifiMetricsProto.WifiLog.SCAN_SUCCESS, mActiveScans.size()); reportScanResults(mScannerImpl.getLatestSingleScanResults()); mActiveScans.clear(); transitionTo(mIdleState); return HANDLED; case CMD_FULL_SCAN_RESULTS: reportFullScanResult((ScanResult) msg.obj, /* bucketsScanned */ msg.arg2); return HANDLED; case CMD_SCAN_FAILED: mWifiMetrics.incrementScanReturnEntry( WifiMetricsProto.WifiLog.SCAN_UNKNOWN, mActiveScans.size()); sendOpFailedToAllAndClear(mActiveScans, WifiScanner.REASON_UNSPECIFIED, "Scan failed"); transitionTo(mIdleState); return HANDLED; default: return NOT_HANDLED; } } } boolean validateScanRequest(ClientInfo ci, int handler, ScanSettings settings, WorkSource workSource) { if (ci == null) { Log.d(TAG, "Failing single scan request ClientInfo not found " + handler); return false; } if (settings.band == WifiScanner.WIFI_BAND_UNSPECIFIED) { if (settings.channels == null || settings.channels.length == 0) { Log.d(TAG, "Failing single scan because channel list was empty"); return false; } } return true; } boolean activeScanSatisfies(ScanSettings settings) { if (mActiveScanSettings == null) { return false; } // there is always one bucket for a single scan WifiNative.BucketSettings activeBucket = mActiveScanSettings.buckets[0]; // validate that all requested channels are being scanned ChannelCollection activeChannels = mChannelHelper.createChannelCollection(); activeChannels.addChannels(activeBucket); if (!activeChannels.containsSettings(settings)) { return false; } // if the request is for a full scan, but there is no ongoing full scan if ((settings.reportEvents & WifiScanner.REPORT_EVENT_FULL_SCAN_RESULT) != 0 && (activeBucket.report_events & WifiScanner.REPORT_EVENT_FULL_SCAN_RESULT) == 0) { return false; } if (settings.hiddenNetworkIds != null) { if (mActiveScanSettings.hiddenNetworkIds == null) { return false; } Set activeHiddenNetworkIds = new HashSet<>(); for (int id : mActiveScanSettings.hiddenNetworkIds) { activeHiddenNetworkIds.add(id); } for (int id : settings.hiddenNetworkIds) { if (!activeHiddenNetworkIds.contains(id)) { return false; } } } return true; } void removeSingleScanRequest(ClientInfo ci, int handler) { if (ci != null) { logScanRequest("removeSingleScanRequest", ci, handler, null, null, null); mPendingScans.removeRequest(ci, handler); mActiveScans.removeRequest(ci, handler); } } void removeSingleScanRequests(ClientInfo ci) { if (ci != null) { logScanRequest("removeSingleScanRequests", ci, -1, null, null, null); mPendingScans.removeAllForClient(ci); mActiveScans.removeAllForClient(ci); } } void tryToStartNewScan() { if (mPendingScans.size() == 0) { // no pending requests return; } mChannelHelper.updateChannels(); // TODO move merging logic to a scheduler WifiNative.ScanSettings settings = new WifiNative.ScanSettings(); settings.num_buckets = 1; WifiNative.BucketSettings bucketSettings = new WifiNative.BucketSettings(); bucketSettings.bucket = 0; bucketSettings.period_ms = 0; bucketSettings.report_events = WifiScanner.REPORT_EVENT_AFTER_EACH_SCAN; ChannelCollection channels = mChannelHelper.createChannelCollection(); HashSet hiddenNetworkIdSet = new HashSet<>(); for (RequestInfo entry : mPendingScans) { channels.addChannels(entry.settings); if (entry.settings.hiddenNetworkIds != null) { for (int i = 0; i < entry.settings.hiddenNetworkIds.length; i++) { hiddenNetworkIdSet.add(entry.settings.hiddenNetworkIds[i]); } } if ((entry.settings.reportEvents & WifiScanner.REPORT_EVENT_FULL_SCAN_RESULT) != 0) { bucketSettings.report_events |= WifiScanner.REPORT_EVENT_FULL_SCAN_RESULT; } } if (hiddenNetworkIdSet.size() > 0) { settings.hiddenNetworkIds = new int[hiddenNetworkIdSet.size()]; int numHiddenNetworks = 0; for (Integer hiddenNetworkId : hiddenNetworkIdSet) { settings.hiddenNetworkIds[numHiddenNetworks++] = hiddenNetworkId; } } channels.fillBucketSettings(bucketSettings, Integer.MAX_VALUE); settings.buckets = new WifiNative.BucketSettings[] {bucketSettings}; if (mScannerImpl.startSingleScan(settings, this)) { // store the active scan settings mActiveScanSettings = settings; // swap pending and active scan requests RequestList tmp = mActiveScans; mActiveScans = mPendingScans; mPendingScans = tmp; // make sure that the pending list is clear mPendingScans.clear(); transitionTo(mScanningState); } else { mWifiMetrics.incrementScanReturnEntry( WifiMetricsProto.WifiLog.SCAN_UNKNOWN, mPendingScans.size()); // notify and cancel failed scans sendOpFailedToAllAndClear(mPendingScans, WifiScanner.REASON_UNSPECIFIED, "Failed to start single scan"); } } void sendOpFailedToAllAndClear(RequestList clientHandlers, int reason, String description) { for (RequestInfo entry : clientHandlers) { logCallback("singleScanFailed", entry.clientInfo, entry.handlerId, "reason=" + reason + ", " + description); entry.reportEvent(WifiScanner.CMD_OP_FAILED, 0, new WifiScanner.OperationResult(reason, description)); } clientHandlers.clear(); } void reportFullScanResult(ScanResult result, int bucketsScanned) { for (RequestInfo entry : mActiveScans) { if (ScanScheduleUtil.shouldReportFullScanResultForSettings(mChannelHelper, result, bucketsScanned, entry.settings, -1)) { entry.reportEvent(WifiScanner.CMD_FULL_SCAN_RESULT, 0, result); } } for (RequestInfo entry : mSingleScanListeners) { entry.reportEvent(WifiScanner.CMD_FULL_SCAN_RESULT, 0, result); } } void reportScanResults(ScanData results) { if (results != null && results.getResults() != null) { if (results.getResults().length > 0) { mWifiMetrics.incrementNonEmptyScanResultCount(); } else { mWifiMetrics.incrementEmptyScanResultCount(); } } ScanData[] allResults = new ScanData[] {results}; for (RequestInfo entry : mActiveScans) { ScanData[] resultsToDeliver = ScanScheduleUtil.filterResultsForSettings( mChannelHelper, allResults, entry.settings, -1); WifiScanner.ParcelableScanData parcelableResultsToDeliver = new WifiScanner.ParcelableScanData(resultsToDeliver); logCallback("singleScanResults", entry.clientInfo, entry.handlerId, describeForLog(resultsToDeliver)); entry.reportEvent(WifiScanner.CMD_SCAN_RESULT, 0, parcelableResultsToDeliver); // make sure the handler is removed entry.reportEvent(WifiScanner.CMD_SINGLE_SCAN_COMPLETED, 0, null); } WifiScanner.ParcelableScanData parcelableAllResults = new WifiScanner.ParcelableScanData(allResults); for (RequestInfo entry : mSingleScanListeners) { logCallback("singleScanResults", entry.clientInfo, entry.handlerId, describeForLog(allResults)); entry.reportEvent(WifiScanner.CMD_SCAN_RESULT, 0, parcelableAllResults); } } } class WifiBackgroundScanStateMachine extends StateMachine implements WifiNative.ScanEventHandler, WifiNative.HotlistEventHandler { private final DefaultState mDefaultState = new DefaultState(); private final StartedState mStartedState = new StartedState(); private final PausedState mPausedState = new PausedState(); private final RequestList mActiveBackgroundScans = new RequestList<>(); private final RequestList mActiveHotlistSettings = new RequestList<>(); WifiBackgroundScanStateMachine(Looper looper) { super("WifiBackgroundScanStateMachine", looper); setLogRecSize(512); setLogOnlyTransitions(false); // CHECKSTYLE:OFF IndentationCheck addState(mDefaultState); addState(mStartedState, mDefaultState); addState(mPausedState, mDefaultState); // CHECKSTYLE:ON IndentationCheck setInitialState(mDefaultState); } public Collection getBackgroundScanSettings(ClientInfo ci) { return mActiveBackgroundScans.getAllSettingsForClient(ci); } public void removeBackgroundScanSettings(ClientInfo ci) { mActiveBackgroundScans.removeAllForClient(ci); updateSchedule(); } public void removeHotlistSettings(ClientInfo ci) { mActiveHotlistSettings.removeAllForClient(ci); resetHotlist(); } @Override public void onScanStatus(int event) { if (DBG) localLog("onScanStatus event received, event=" + event); switch(event) { case WifiNative.WIFI_SCAN_RESULTS_AVAILABLE: case WifiNative.WIFI_SCAN_THRESHOLD_NUM_SCANS: case WifiNative.WIFI_SCAN_THRESHOLD_PERCENT: sendMessage(CMD_SCAN_RESULTS_AVAILABLE); break; case WifiNative.WIFI_SCAN_FAILED: sendMessage(CMD_SCAN_FAILED); break; default: Log.e(TAG, "Unknown scan status event: " + event); break; } } @Override public void onFullScanResult(ScanResult fullScanResult, int bucketsScanned) { if (DBG) localLog("onFullScanResult received"); sendMessage(CMD_FULL_SCAN_RESULTS, 0, bucketsScanned, fullScanResult); } @Override public void onScanPaused(ScanData scanData[]) { if (DBG) localLog("onScanPaused received"); sendMessage(CMD_SCAN_PAUSED, scanData); } @Override public void onScanRestarted() { if (DBG) localLog("onScanRestarted received"); sendMessage(CMD_SCAN_RESTARTED); } @Override public void onHotlistApFound(ScanResult[] results) { if (DBG) localLog("onHotlistApFound event received"); sendMessage(CMD_HOTLIST_AP_FOUND, 0, 0, results); } @Override public void onHotlistApLost(ScanResult[] results) { if (DBG) localLog("onHotlistApLost event received"); sendMessage(CMD_HOTLIST_AP_LOST, 0, 0, results); } class DefaultState extends State { @Override public void enter() { if (DBG) localLog("DefaultState"); mActiveBackgroundScans.clear(); mActiveHotlistSettings.clear(); } @Override public boolean processMessage(Message msg) { switch (msg.what) { case CMD_DRIVER_LOADED: // TODO this should be moved to a common location since it is used outside // of this state machine. It is ok right now because the driver loaded event // is sent to this state machine first. if (mScannerImpl == null) { mScannerImpl = mScannerImplFactory.create(mContext, mLooper, mClock); mChannelHelper = mScannerImpl.getChannelHelper(); } mBackgroundScheduler = new BackgroundScanScheduler(mChannelHelper); WifiNative.ScanCapabilities capabilities = new WifiNative.ScanCapabilities(); if (!mScannerImpl.getScanCapabilities(capabilities)) { loge("could not get scan capabilities"); return HANDLED; } mBackgroundScheduler.setMaxBuckets(capabilities.max_scan_buckets); mBackgroundScheduler.setMaxApPerScan(capabilities.max_ap_cache_per_scan); Log.i(TAG, "wifi driver loaded with scan capabilities: " + "max buckets=" + capabilities.max_scan_buckets); transitionTo(mStartedState); return HANDLED; case CMD_DRIVER_UNLOADED: Log.i(TAG, "wifi driver unloaded"); transitionTo(mDefaultState); break; 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_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 void exit() { sendBackgroundScanFailedToAllAndClear( WifiScanner.REASON_UNSPECIFIED, "Scan was interrupted"); sendHotlistFailedToAllAndClear( WifiScanner.REASON_UNSPECIFIED, "Scan was interrupted"); mScannerImpl.cleanup(); } @Override public boolean processMessage(Message msg) { ClientInfo ci = mClients.get(msg.replyTo); switch (msg.what) { case CMD_DRIVER_LOADED: return NOT_HANDLED; case CMD_DRIVER_UNLOADED: return NOT_HANDLED; case WifiScanner.CMD_START_BACKGROUND_SCAN: { mWifiMetrics.incrementBackgroundScanCount(); Bundle scanParams = (Bundle) msg.obj; if (scanParams == null) { replyFailed(msg, WifiScanner.REASON_INVALID_REQUEST, "params null"); return HANDLED; } scanParams.setDefusable(true); ScanSettings scanSettings = scanParams.getParcelable(WifiScanner.SCAN_PARAMS_SCAN_SETTINGS_KEY); WorkSource workSource = scanParams.getParcelable(WifiScanner.SCAN_PARAMS_WORK_SOURCE_KEY); if (addBackgroundScanRequest(ci, msg.arg2, scanSettings, workSource)) { replySucceeded(msg); } else { replyFailed(msg, WifiScanner.REASON_INVALID_REQUEST, "bad request"); } break; } case WifiScanner.CMD_STOP_BACKGROUND_SCAN: removeBackgroundScanRequest(ci, msg.arg2); break; case WifiScanner.CMD_GET_SCAN_RESULTS: reportScanResults(mScannerImpl.getLatestBatchedScanResults(true)); replySucceeded(msg); break; case WifiScanner.CMD_SET_HOTLIST: if (addHotlist(ci, msg.arg2, (WifiScanner.HotlistSettings) msg.obj)) { replySucceeded(msg); } else { replyFailed(msg, WifiScanner.REASON_INVALID_REQUEST, "bad request"); } break; case WifiScanner.CMD_RESET_HOTLIST: removeHotlist(ci, msg.arg2); break; case CMD_SCAN_RESULTS_AVAILABLE: reportScanResults(mScannerImpl.getLatestBatchedScanResults(true)); break; case CMD_FULL_SCAN_RESULTS: reportFullScanResult((ScanResult) msg.obj, /* bucketsScanned */ msg.arg2); break; case CMD_HOTLIST_AP_FOUND: reportHotlistResults(WifiScanner.CMD_AP_FOUND, (ScanResult[]) msg.obj); break; case CMD_HOTLIST_AP_LOST: reportHotlistResults(WifiScanner.CMD_AP_LOST, (ScanResult[]) msg.obj); break; case CMD_SCAN_PAUSED: reportScanResults((ScanData[]) msg.obj); transitionTo(mPausedState); break; case CMD_SCAN_FAILED: Log.e(TAG, "WifiScanner background scan gave CMD_SCAN_FAILED"); sendBackgroundScanFailedToAllAndClear( WifiScanner.REASON_UNSPECIFIED, "Background Scan failed"); 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) { switch (msg.what) { case CMD_SCAN_RESTARTED: transitionTo(mStartedState); break; default: deferMessage(msg); break; } return HANDLED; } } private boolean addBackgroundScanRequest(ClientInfo ci, int handler, ScanSettings settings, WorkSource workSource) { // 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) { loge("Failing scan request because periodInMs is " + settings.periodInMs + ", min scan period is: " + WifiScanner.MIN_SCAN_PERIOD_MS); return false; } if (settings.band == WifiScanner.WIFI_BAND_UNSPECIFIED && settings.channels == null) { loge("Channels was null with unspecified band"); return false; } if (settings.band == WifiScanner.WIFI_BAND_UNSPECIFIED && settings.channels.length == 0) { loge("No channels specified"); return false; } int minSupportedPeriodMs = mChannelHelper.estimateScanDuration(settings); if (settings.periodInMs < minSupportedPeriodMs) { loge("Failing scan request because minSupportedPeriodMs is " + minSupportedPeriodMs + " but the request wants " + settings.periodInMs); return false; } // check truncated binary exponential back off scan settings if (settings.maxPeriodInMs != 0 && settings.maxPeriodInMs != settings.periodInMs) { if (settings.maxPeriodInMs < settings.periodInMs) { loge("Failing scan request because maxPeriodInMs is " + settings.maxPeriodInMs + " but less than periodInMs " + settings.periodInMs); return false; } if (settings.maxPeriodInMs > WifiScanner.MAX_SCAN_PERIOD_MS) { loge("Failing scan request because maxSupportedPeriodMs is " + WifiScanner.MAX_SCAN_PERIOD_MS + " but the request wants " + settings.maxPeriodInMs); return false; } if (settings.stepCount < 1) { loge("Failing scan request because stepCount is " + settings.stepCount + " which is less than 1"); return false; } } logScanRequest("addBackgroundScanRequest", ci, handler, null, settings, null); mActiveBackgroundScans.addRequest(ci, handler, workSource, settings); if (updateSchedule()) { return true; } else { mActiveBackgroundScans.removeRequest(ci, handler); localLog("Failing scan request because failed to reset scan"); return false; } } private boolean updateSchedule() { if (mChannelHelper == null || mBackgroundScheduler == null || mScannerImpl == null) { loge("Failed to update schedule because WifiScanningService is not initialized"); return false; } mChannelHelper.updateChannels(); Collection settings = mActiveBackgroundScans.getAllSettings(); mBackgroundScheduler.updateSchedule(settings); WifiNative.ScanSettings schedule = mBackgroundScheduler.getSchedule(); if (ScanScheduleUtil.scheduleEquals(mPreviousSchedule, schedule)) { if (DBG) Log.d(TAG, "schedule updated with no change"); return true; } mPreviousSchedule = schedule; if (schedule.num_buckets == 0) { mScannerImpl.stopBatchedScan(); if (DBG) Log.d(TAG, "scan stopped"); return true; } else { localLog("starting scan: " + "base period=" + schedule.base_period_ms + ", max ap per scan=" + schedule.max_ap_per_scan + ", batched scans=" + schedule.report_threshold_num_scans); for (int b = 0; b < schedule.num_buckets; b++) { WifiNative.BucketSettings bucket = schedule.buckets[b]; localLog("bucket " + bucket.bucket + " (" + bucket.period_ms + "ms)" + "[" + bucket.report_events + "]: " + ChannelHelper.toString(bucket)); } if (mScannerImpl.startBatchedScan(schedule, this)) { if (DBG) { Log.d(TAG, "scan restarted with " + schedule.num_buckets + " bucket(s) and base period: " + schedule.base_period_ms); } return true; } else { mPreviousSchedule = null; loge("error starting scan: " + "base period=" + schedule.base_period_ms + ", max ap per scan=" + schedule.max_ap_per_scan + ", batched scans=" + schedule.report_threshold_num_scans); for (int b = 0; b < schedule.num_buckets; b++) { WifiNative.BucketSettings bucket = schedule.buckets[b]; loge("bucket " + bucket.bucket + " (" + bucket.period_ms + "ms)" + "[" + bucket.report_events + "]: " + ChannelHelper.toString(bucket)); } return false; } } } private void removeBackgroundScanRequest(ClientInfo ci, int handler) { if (ci != null) { ScanSettings settings = mActiveBackgroundScans.removeRequest(ci, handler); logScanRequest("removeBackgroundScanRequest", ci, handler, null, settings, null); updateSchedule(); } } private void reportFullScanResult(ScanResult result, int bucketsScanned) { for (RequestInfo entry : mActiveBackgroundScans) { ClientInfo ci = entry.clientInfo; int handler = entry.handlerId; ScanSettings settings = entry.settings; if (mBackgroundScheduler.shouldReportFullScanResultForSettings( result, bucketsScanned, settings)) { ScanResult newResult = new ScanResult(result); if (result.informationElements != null) { newResult.informationElements = result.informationElements.clone(); } else { newResult.informationElements = null; } ci.reportEvent(WifiScanner.CMD_FULL_SCAN_RESULT, 0, handler, newResult); } } } private void reportScanResults(ScanData[] results) { for (ScanData result : results) { if (result != null && result.getResults() != null) { if (result.getResults().length > 0) { mWifiMetrics.incrementNonEmptyScanResultCount(); } else { mWifiMetrics.incrementEmptyScanResultCount(); } } } for (RequestInfo entry : mActiveBackgroundScans) { ClientInfo ci = entry.clientInfo; int handler = entry.handlerId; ScanSettings settings = entry.settings; ScanData[] resultsToDeliver = mBackgroundScheduler.filterResultsForSettings(results, settings); if (resultsToDeliver != null) { logCallback("backgroundScanResults", ci, handler, describeForLog(resultsToDeliver)); WifiScanner.ParcelableScanData parcelableScanData = new WifiScanner.ParcelableScanData(resultsToDeliver); ci.reportEvent(WifiScanner.CMD_SCAN_RESULT, 0, handler, parcelableScanData); } } } private void sendBackgroundScanFailedToAllAndClear(int reason, String description) { for (RequestInfo entry : mActiveBackgroundScans) { ClientInfo ci = entry.clientInfo; int handler = entry.handlerId; ci.reportEvent(WifiScanner.CMD_OP_FAILED, 0, handler, new WifiScanner.OperationResult(reason, description)); } mActiveBackgroundScans.clear(); } private boolean addHotlist(ClientInfo ci, int handler, WifiScanner.HotlistSettings settings) { if (ci == null) { Log.d(TAG, "Failing hotlist request ClientInfo not found " + handler); return false; } mActiveHotlistSettings.addRequest(ci, handler, null, settings); resetHotlist(); return true; } private void removeHotlist(ClientInfo ci, int handler) { if (ci != null) { mActiveHotlistSettings.removeRequest(ci, handler); resetHotlist(); } } private void resetHotlist() { if (mScannerImpl == null) { loge("Failed to update hotlist because WifiScanningService is not initialized"); return; } Collection settings = mActiveHotlistSettings.getAllSettings(); int num_hotlist_ap = 0; for (WifiScanner.HotlistSettings s : settings) { num_hotlist_ap += s.bssidInfos.length; } if (num_hotlist_ap == 0) { mScannerImpl.resetHotlist(); } else { BssidInfo[] bssidInfos = new BssidInfo[num_hotlist_ap]; int apLostThreshold = Integer.MAX_VALUE; int index = 0; for (WifiScanner.HotlistSettings s : settings) { for (int i = 0; i < s.bssidInfos.length; i++, index++) { bssidInfos[index] = s.bssidInfos[i]; } if (s.apLostThreshold < apLostThreshold) { apLostThreshold = s.apLostThreshold; } } WifiScanner.HotlistSettings mergedSettings = new WifiScanner.HotlistSettings(); mergedSettings.bssidInfos = bssidInfos; mergedSettings.apLostThreshold = apLostThreshold; mScannerImpl.setHotlist(mergedSettings, this); } } private void reportHotlistResults(int what, ScanResult[] results) { if (DBG) localLog("reportHotlistResults " + what + " results " + results.length); for (RequestInfo entry : mActiveHotlistSettings) { ClientInfo ci = entry.clientInfo; int handler = entry.handlerId; WifiScanner.HotlistSettings settings = entry.settings; 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); ci.reportEvent(what, 0, handler, parcelableScanResults); } } private void sendHotlistFailedToAllAndClear(int reason, String description) { for (RequestInfo entry : mActiveHotlistSettings) { ClientInfo ci = entry.clientInfo; int handler = entry.handlerId; ci.reportEvent(WifiScanner.CMD_OP_FAILED, 0, handler, new WifiScanner.OperationResult(reason, description)); } mActiveHotlistSettings.clear(); } } /** * PNO scan state machine has 5 states: * -Default State * -Started State * -Hw Pno Scan state * -Single Scan state * -Sw Pno Scan state * * These are the main state transitions: * 1. Start at |Default State| * 2. Move to |Started State| when we get the |WIFI_SCAN_AVAILABLE| broadcast from WifiManager. * 3. When a new PNO scan request comes in: * a.1. Switch to |Hw Pno Scan state| when the device supports HW PNO * (This could either be HAL based ePNO or supplicant based PNO). * a.2. In |Hw Pno Scan state| when PNO scan results are received, check if the result * contains IE (information elements). If yes, send the results to the client, else * switch to |Single Scan state| and send the result to the client when the scan result * is obtained. * b.1. Switch to |Sw Pno Scan state| when the device does not supports HW PNO * (This is for older devices which do not support HW PNO and for connected PNO on * devices which support supplicant based PNO) * b.2. In |Sw Pno Scan state| send the result to the client when the background scan result * is obtained * * Note: PNO scans only work for a single client today. We don't have support in HW to support * multiple requests at the same time, so will need non-trivial changes to support (if at all * possible) in WifiScanningService. */ class WifiPnoScanStateMachine extends StateMachine implements WifiNative.PnoEventHandler { private final DefaultState mDefaultState = new DefaultState(); private final StartedState mStartedState = new StartedState(); private final HwPnoScanState mHwPnoScanState = new HwPnoScanState(); private final SwPnoScanState mSwPnoScanState = new SwPnoScanState(); private final SingleScanState mSingleScanState = new SingleScanState(); private InternalClientInfo mInternalClientInfo; private final RequestList> mActivePnoScans = new RequestList<>(); WifiPnoScanStateMachine(Looper looper) { super("WifiPnoScanStateMachine", looper); setLogRecSize(512); setLogOnlyTransitions(false); // CHECKSTYLE:OFF IndentationCheck addState(mDefaultState); addState(mStartedState, mDefaultState); addState(mHwPnoScanState, mStartedState); addState(mSingleScanState, mHwPnoScanState); addState(mSwPnoScanState, mStartedState); // CHECKSTYLE:ON IndentationCheck setInitialState(mDefaultState); } public void removePnoSettings(ClientInfo ci) { mActivePnoScans.removeAllForClient(ci); transitionTo(mStartedState); } @Override public void onPnoNetworkFound(ScanResult[] results) { if (DBG) localLog("onWifiPnoNetworkFound event received"); sendMessage(CMD_PNO_NETWORK_FOUND, 0, 0, results); } @Override public void onPnoScanFailed() { if (DBG) localLog("onWifiPnoScanFailed event received"); sendMessage(CMD_PNO_SCAN_FAILED, 0, 0, null); } class DefaultState extends State { @Override public void enter() { if (DBG) localLog("DefaultState"); } @Override public boolean processMessage(Message msg) { switch (msg.what) { case CMD_DRIVER_LOADED: transitionTo(mStartedState); break; case CMD_DRIVER_UNLOADED: transitionTo(mDefaultState); break; case WifiScanner.CMD_START_PNO_SCAN: case WifiScanner.CMD_STOP_PNO_SCAN: replyFailed(msg, WifiScanner.REASON_UNSPECIFIED, "not available"); break; case CMD_PNO_NETWORK_FOUND: case CMD_PNO_SCAN_FAILED: case WifiScanner.CMD_SCAN_RESULT: case WifiScanner.CMD_OP_FAILED: loge("Unexpected message " + msg.what); break; default: return NOT_HANDLED; } return HANDLED; } } class StartedState extends State { @Override public void enter() { if (DBG) localLog("StartedState"); } @Override public void exit() { sendPnoScanFailedToAllAndClear( WifiScanner.REASON_UNSPECIFIED, "Scan was interrupted"); } @Override public boolean processMessage(Message msg) { ClientInfo ci = mClients.get(msg.replyTo); switch (msg.what) { case WifiScanner.CMD_START_PNO_SCAN: Bundle pnoParams = (Bundle) msg.obj; if (pnoParams == null) { replyFailed(msg, WifiScanner.REASON_INVALID_REQUEST, "params null"); return HANDLED; } pnoParams.setDefusable(true); PnoSettings pnoSettings = pnoParams.getParcelable(WifiScanner.PNO_PARAMS_PNO_SETTINGS_KEY); // This message is handled after the transition to SwPnoScan/HwPnoScan state deferMessage(msg); if (mScannerImpl.isHwPnoSupported(pnoSettings.isConnected)) { transitionTo(mHwPnoScanState); } else { transitionTo(mSwPnoScanState); } break; case WifiScanner.CMD_STOP_PNO_SCAN: replyFailed(msg, WifiScanner.REASON_UNSPECIFIED, "no scan running"); break; default: return NOT_HANDLED; } return HANDLED; } } class HwPnoScanState extends State { @Override public void enter() { if (DBG) localLog("HwPnoScanState"); } @Override public void exit() { // Reset PNO scan in ScannerImpl before we exit. mScannerImpl.resetHwPnoList(); removeInternalClient(); } @Override public boolean processMessage(Message msg) { ClientInfo ci = mClients.get(msg.replyTo); switch (msg.what) { case WifiScanner.CMD_START_PNO_SCAN: Bundle pnoParams = (Bundle) msg.obj; if (pnoParams == null) { replyFailed(msg, WifiScanner.REASON_INVALID_REQUEST, "params null"); return HANDLED; } pnoParams.setDefusable(true); PnoSettings pnoSettings = pnoParams.getParcelable(WifiScanner.PNO_PARAMS_PNO_SETTINGS_KEY); ScanSettings scanSettings = pnoParams.getParcelable(WifiScanner.PNO_PARAMS_SCAN_SETTINGS_KEY); if (addHwPnoScanRequest(ci, msg.arg2, scanSettings, pnoSettings)) { replySucceeded(msg); } else { replyFailed(msg, WifiScanner.REASON_INVALID_REQUEST, "bad request"); transitionTo(mStartedState); } break; case WifiScanner.CMD_STOP_PNO_SCAN: removeHwPnoScanRequest(ci, msg.arg2); transitionTo(mStartedState); break; case CMD_PNO_NETWORK_FOUND: ScanResult[] scanResults = ((ScanResult[]) msg.obj); if (isSingleScanNeeded(scanResults)) { ScanSettings activeScanSettings = getScanSettings(); if (activeScanSettings == null) { sendPnoScanFailedToAllAndClear( WifiScanner.REASON_UNSPECIFIED, "couldn't retrieve setting"); transitionTo(mStartedState); } else { addSingleScanRequest(activeScanSettings); transitionTo(mSingleScanState); } } else { reportPnoNetworkFound((ScanResult[]) msg.obj); } break; case CMD_PNO_SCAN_FAILED: sendPnoScanFailedToAllAndClear( WifiScanner.REASON_UNSPECIFIED, "pno scan failed"); transitionTo(mStartedState); break; default: return NOT_HANDLED; } return HANDLED; } } class SingleScanState extends State { @Override public void enter() { if (DBG) localLog("SingleScanState"); } @Override public boolean processMessage(Message msg) { ClientInfo ci = mClients.get(msg.replyTo); switch (msg.what) { case WifiScanner.CMD_SCAN_RESULT: WifiScanner.ParcelableScanData parcelableScanData = (WifiScanner.ParcelableScanData) msg.obj; ScanData[] scanDatas = parcelableScanData.getResults(); ScanData lastScanData = scanDatas[scanDatas.length - 1]; reportPnoNetworkFound(lastScanData.getResults()); transitionTo(mHwPnoScanState); break; case WifiScanner.CMD_OP_FAILED: sendPnoScanFailedToAllAndClear( WifiScanner.REASON_UNSPECIFIED, "single scan failed"); transitionTo(mStartedState); break; default: return NOT_HANDLED; } return HANDLED; } } class SwPnoScanState extends State { private final ArrayList mSwPnoFullScanResults = new ArrayList<>(); @Override public void enter() { if (DBG) localLog("SwPnoScanState"); mSwPnoFullScanResults.clear(); } @Override public void exit() { removeInternalClient(); } @Override public boolean processMessage(Message msg) { ClientInfo ci = mClients.get(msg.replyTo); switch (msg.what) { case WifiScanner.CMD_START_PNO_SCAN: Bundle pnoParams = (Bundle) msg.obj; if (pnoParams == null) { replyFailed(msg, WifiScanner.REASON_INVALID_REQUEST, "params null"); return HANDLED; } pnoParams.setDefusable(true); PnoSettings pnoSettings = pnoParams.getParcelable(WifiScanner.PNO_PARAMS_PNO_SETTINGS_KEY); ScanSettings scanSettings = pnoParams.getParcelable(WifiScanner.PNO_PARAMS_SCAN_SETTINGS_KEY); if (addSwPnoScanRequest(ci, msg.arg2, scanSettings, pnoSettings)) { replySucceeded(msg); } else { replyFailed(msg, WifiScanner.REASON_INVALID_REQUEST, "bad request"); transitionTo(mStartedState); } break; case WifiScanner.CMD_STOP_PNO_SCAN: removeSwPnoScanRequest(ci, msg.arg2); transitionTo(mStartedState); break; case WifiScanner.CMD_FULL_SCAN_RESULT: // Aggregate full scan results until we get the |CMD_SCAN_RESULT| message mSwPnoFullScanResults.add((ScanResult) msg.obj); break; case WifiScanner.CMD_SCAN_RESULT: ScanResult[] scanResults = mSwPnoFullScanResults.toArray( new ScanResult[mSwPnoFullScanResults.size()]); reportPnoNetworkFound(scanResults); mSwPnoFullScanResults.clear(); break; case WifiScanner.CMD_OP_FAILED: sendPnoScanFailedToAllAndClear( WifiScanner.REASON_UNSPECIFIED, "background scan failed"); transitionTo(mStartedState); break; default: return NOT_HANDLED; } return HANDLED; } } private WifiNative.PnoSettings convertPnoSettingsToNative(PnoSettings pnoSettings) { WifiNative.PnoSettings nativePnoSetting = new WifiNative.PnoSettings(); nativePnoSetting.min5GHzRssi = pnoSettings.min5GHzRssi; nativePnoSetting.min24GHzRssi = pnoSettings.min24GHzRssi; nativePnoSetting.initialScoreMax = pnoSettings.initialScoreMax; nativePnoSetting.currentConnectionBonus = pnoSettings.currentConnectionBonus; nativePnoSetting.sameNetworkBonus = pnoSettings.sameNetworkBonus; nativePnoSetting.secureBonus = pnoSettings.secureBonus; nativePnoSetting.band5GHzBonus = pnoSettings.band5GHzBonus; nativePnoSetting.isConnected = pnoSettings.isConnected; nativePnoSetting.networkList = new WifiNative.PnoNetwork[pnoSettings.networkList.length]; for (int i = 0; i < pnoSettings.networkList.length; i++) { nativePnoSetting.networkList[i] = new WifiNative.PnoNetwork(); nativePnoSetting.networkList[i].ssid = pnoSettings.networkList[i].ssid; nativePnoSetting.networkList[i].networkId = pnoSettings.networkList[i].networkId; nativePnoSetting.networkList[i].priority = pnoSettings.networkList[i].priority; nativePnoSetting.networkList[i].flags = pnoSettings.networkList[i].flags; nativePnoSetting.networkList[i].auth_bit_field = pnoSettings.networkList[i].authBitField; } return nativePnoSetting; } // Retrieve the only active scan settings. private ScanSettings getScanSettings() { for (Pair settingsPair : mActivePnoScans.getAllSettings()) { return settingsPair.second; } return null; } private void removeInternalClient() { if (mInternalClientInfo != null) { mInternalClientInfo.cleanup(); mInternalClientInfo = null; } else { Log.w(TAG, "No Internal client for PNO"); } } private void addInternalClient(ClientInfo ci) { if (mInternalClientInfo == null) { mInternalClientInfo = new InternalClientInfo(ci.getUid(), new Messenger(this.getHandler())); mInternalClientInfo.register(); } else { Log.w(TAG, "Internal client for PNO already exists"); } } private void addPnoScanRequest(ClientInfo ci, int handler, ScanSettings scanSettings, PnoSettings pnoSettings) { mActivePnoScans.addRequest(ci, handler, WifiStateMachine.WIFI_WORK_SOURCE, Pair.create(pnoSettings, scanSettings)); addInternalClient(ci); } private Pair removePnoScanRequest(ClientInfo ci, int handler) { Pair settings = mActivePnoScans.removeRequest(ci, handler); return settings; } private boolean addHwPnoScanRequest(ClientInfo ci, int handler, ScanSettings scanSettings, PnoSettings pnoSettings) { if (ci == null) { Log.d(TAG, "Failing scan request ClientInfo not found " + handler); return false; } if (!mActivePnoScans.isEmpty()) { loge("Failing scan request because there is already an active scan"); return false; } WifiNative.PnoSettings nativePnoSettings = convertPnoSettingsToNative(pnoSettings); if (!mScannerImpl.setHwPnoList(nativePnoSettings, mPnoScanStateMachine)) { return false; } logScanRequest("addHwPnoScanRequest", ci, handler, null, scanSettings, pnoSettings); addPnoScanRequest(ci, handler, scanSettings, pnoSettings); // HW PNO is supported, check if we need a background scan running for this. if (mScannerImpl.shouldScheduleBackgroundScanForHwPno()) { addBackgroundScanRequest(scanSettings); } return true; } private void removeHwPnoScanRequest(ClientInfo ci, int handler) { if (ci != null) { Pair settings = removePnoScanRequest(ci, handler); logScanRequest("removeHwPnoScanRequest", ci, handler, null, settings.second, settings.first); } } private boolean addSwPnoScanRequest(ClientInfo ci, int handler, ScanSettings scanSettings, PnoSettings pnoSettings) { if (ci == null) { Log.d(TAG, "Failing scan request ClientInfo not found " + handler); return false; } if (!mActivePnoScans.isEmpty()) { loge("Failing scan request because there is already an active scan"); return false; } logScanRequest("addSwPnoScanRequest", ci, handler, null, scanSettings, pnoSettings); addPnoScanRequest(ci, handler, scanSettings, pnoSettings); // HW PNO is not supported, we need to revert to normal background scans and // report events after each scan and we need full scan results to get the IE information scanSettings.reportEvents = WifiScanner.REPORT_EVENT_AFTER_EACH_SCAN | WifiScanner.REPORT_EVENT_FULL_SCAN_RESULT; addBackgroundScanRequest(scanSettings); return true; } private void removeSwPnoScanRequest(ClientInfo ci, int handler) { if (ci != null) { Pair settings = removePnoScanRequest(ci, handler); logScanRequest("removeSwPnoScanRequest", ci, handler, null, settings.second, settings.first); } } private void reportPnoNetworkFound(ScanResult[] results) { WifiScanner.ParcelableScanResults parcelableScanResults = new WifiScanner.ParcelableScanResults(results); for (RequestInfo> entry : mActivePnoScans) { ClientInfo ci = entry.clientInfo; int handler = entry.handlerId; logCallback("pnoNetworkFound", ci, handler, describeForLog(results)); ci.reportEvent( WifiScanner.CMD_PNO_NETWORK_FOUND, 0, handler, parcelableScanResults); } } private void sendPnoScanFailedToAllAndClear(int reason, String description) { for (RequestInfo> entry : mActivePnoScans) { ClientInfo ci = entry.clientInfo; int handler = entry.handlerId; ci.reportEvent(WifiScanner.CMD_OP_FAILED, 0, handler, new WifiScanner.OperationResult(reason, description)); } mActivePnoScans.clear(); } private void addBackgroundScanRequest(ScanSettings settings) { if (DBG) localLog("Starting background scan"); if (mInternalClientInfo != null) { mInternalClientInfo.sendRequestToClientHandler( WifiScanner.CMD_START_BACKGROUND_SCAN, settings, WifiStateMachine.WIFI_WORK_SOURCE); } } private void addSingleScanRequest(ScanSettings settings) { if (DBG) localLog("Starting single scan"); if (mInternalClientInfo != null) { mInternalClientInfo.sendRequestToClientHandler( WifiScanner.CMD_START_SINGLE_SCAN, settings, WifiStateMachine.WIFI_WORK_SOURCE); } } /** * Checks if IE are present in scan data, if no single scan is needed to report event to * client */ private boolean isSingleScanNeeded(ScanResult[] scanResults) { for (ScanResult scanResult : scanResults) { if (scanResult.informationElements != null && scanResult.informationElements.length > 0) { return false; } } return true; } } private abstract class ClientInfo { private final int mUid; private final WorkSource mWorkSource; private boolean mScanWorkReported = false; protected final Messenger mMessenger; ClientInfo(int uid, Messenger messenger) { mUid = uid; mMessenger = messenger; mWorkSource = new WorkSource(uid); } /** * Register this client to main client map. */ public void register() { mClients.put(mMessenger, this); } /** * Unregister this client from main client map. */ private void unregister() { mClients.remove(mMessenger); } public void cleanup() { mSingleScanListeners.removeAllForClient(this); mSingleScanStateMachine.removeSingleScanRequests(this); mBackgroundScanStateMachine.removeBackgroundScanSettings(this); mBackgroundScanStateMachine.removeHotlistSettings(this); unregister(); localLog("Successfully stopped all requests for client " + this); } public int getUid() { return mUid; } public void reportEvent(int what, int arg1, int arg2) { reportEvent(what, arg1, arg2, null); } // This has to be implemented by subclasses to report events back to clients. public abstract void reportEvent(int what, int arg1, int arg2, Object obj); // TODO(b/27903217): Blame scan on provided work source private void reportBatchedScanStart() { if (mUid == 0) return; int csph = getCsph(); try { mBatteryStats.noteWifiBatchedScanStartedFromSource(mWorkSource, csph); } catch (RemoteException e) { logw("failed to report scan work: " + e.toString()); } } private void reportBatchedScanStop() { if (mUid == 0) return; try { mBatteryStats.noteWifiBatchedScanStoppedFromSource(mWorkSource); } catch (RemoteException e) { logw("failed to cleanup scan work: " + e.toString()); } } // TODO migrate batterystats to accept scan duration per hour instead of csph private int getCsph() { int totalScanDurationPerHour = 0; Collection settingsList = mBackgroundScanStateMachine.getBackgroundScanSettings(this); for (ScanSettings settings : settingsList) { int scanDurationMs = mChannelHelper.estimateScanDuration(settings); int scans_per_Hour = settings.periodInMs == 0 ? 1 : (3600 * 1000) / settings.periodInMs; totalScanDurationPerHour += scanDurationMs * scans_per_Hour; } return totalScanDurationPerHour / ChannelHelper.SCAN_PERIOD_PER_CHANNEL_MS; } public void reportScanWorkUpdate() { if (mScanWorkReported) { reportBatchedScanStop(); mScanWorkReported = false; } if (mBackgroundScanStateMachine.getBackgroundScanSettings(this).isEmpty()) { reportBatchedScanStart(); mScanWorkReported = true; } } @Override public String toString() { return "ClientInfo[uid=" + mUid + "," + mMessenger + "]"; } } /** * This class is used to represent external clients to the WifiScanning Service. */ private class ExternalClientInfo extends ClientInfo { private final AsyncChannel mChannel; /** * Indicates if the client is still connected * If the client is no longer connected then messages to it will be silently dropped */ private boolean mDisconnected = false; ExternalClientInfo(int uid, Messenger messenger, AsyncChannel c) { super(uid, messenger); mChannel = c; if (DBG) localLog("New client, channel: " + c); } @Override public void reportEvent(int what, int arg1, int arg2, Object obj) { if (!mDisconnected) { mChannel.sendMessage(what, arg1, arg2, obj); } } @Override public void cleanup() { mDisconnected = true; // Internal clients should not have any wifi change requests. So, keeping this cleanup // only for external client because this will otherwise cause an infinite recursion // when the internal client in WifiChangeStateMachine is cleaned up. mWifiChangeStateMachine.removeWifiChangeHandler(this); mPnoScanStateMachine.removePnoSettings(this); super.cleanup(); } } /** * This class is used to represent internal clients to the WifiScanning Service. This is needed * for communicating between State Machines. * This leaves the onReportEvent method unimplemented, so that the clients have the freedom * to handle the events as they need. */ private class InternalClientInfo extends ClientInfo { private static final int INTERNAL_CLIENT_HANDLER = 0; /** * The UID here is used to proxy the original external requester UID. */ InternalClientInfo(int requesterUid, Messenger messenger) { super(requesterUid, messenger); } @Override public void reportEvent(int what, int arg1, int arg2, Object obj) { Message message = Message.obtain(); message.what = what; message.arg1 = arg1; message.arg2 = arg2; message.obj = obj; try { mMessenger.send(message); } catch (RemoteException e) { loge("Failed to send message: " + what); } } /** * Send a message to the client handler which should reroute the message to the appropriate * state machine. */ public void sendRequestToClientHandler(int what, ScanSettings settings, WorkSource workSource) { Message msg = Message.obtain(); msg.what = what; msg.arg2 = INTERNAL_CLIENT_HANDLER; if (settings != null) { Bundle bundle = new Bundle(); bundle.putParcelable(WifiScanner.SCAN_PARAMS_SCAN_SETTINGS_KEY, settings); bundle.putParcelable(WifiScanner.SCAN_PARAMS_WORK_SOURCE_KEY, workSource); msg.obj = bundle; } msg.replyTo = mMessenger; msg.sendingUid = getUid(); mClientHandler.sendMessage(msg); } /** * Send a message to the client handler which should reroute the message to the appropriate * state machine. */ public void sendRequestToClientHandler(int what) { sendRequestToClientHandler(what, null, null); } @Override public String toString() { return "InternalClientInfo[]"; } } 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! } } /** * Wifi Change state machine is used to handle any wifi change tracking requests. * TODO: This state machine doesn't handle driver loading/unloading yet. */ class WifiChangeStateMachine extends StateMachine implements WifiNative.SignificantWifiChangeEventHandler { 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"; private PendingIntent mTimeoutIntent; private ScanResult[] mCurrentBssids; private InternalClientInfo mInternalClientInfo; private final Set> mActiveWifiChangeHandlers = new HashSet<>(); WifiChangeStateMachine(Looper looper) { super("SignificantChangeStateMachine", looper); // CHECKSTYLE:OFF IndentationCheck addState(mDefaultState); addState(mStationaryState, mDefaultState); addState(mMovingState, mDefaultState); // CHECKSTYLE:ON IndentationCheck setInitialState(mDefaultState); } public void removeWifiChangeHandler(ClientInfo ci) { Iterator> iter = mActiveWifiChangeHandlers.iterator(); while (iter.hasNext()) { Pair entry = iter.next(); if (entry.first == ci) { iter.remove(); } } untrackSignificantWifiChangeOnEmpty(); } 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); ClientInfo ci = mClients.get(msg.replyTo); switch (msg.what) { case WifiScanner.CMD_START_TRACKING_CHANGE: if (addWifiChangeHandler(ci, msg.arg2)) { replySucceeded(msg); transitionTo(mMovingState); } else { replyFailed(msg, WifiScanner.REASON_INVALID_REQUEST, "bad request"); } break; case WifiScanner.CMD_STOP_TRACKING_CHANGE: // nothing to do break; case WifiScanner.CMD_CONFIGURE_WIFI_CHANGE: /* save configuration till we transition to moving state */ deferMessage(msg); break; case WifiScanner.CMD_SCAN_RESULT: // nothing to do 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); ClientInfo ci = mClients.get(msg.replyTo); switch (msg.what) { case WifiScanner.CMD_START_TRACKING_CHANGE: if (addWifiChangeHandler(ci, msg.arg2)) { replySucceeded(msg); } else { replyFailed(msg, WifiScanner.REASON_INVALID_REQUEST, "bad request"); } break; case WifiScanner.CMD_STOP_TRACKING_CHANGE: removeWifiChangeHandler(ci, msg.arg2); break; case WifiScanner.CMD_CONFIGURE_WIFI_CHANGE: /* save configuration till we transition to moving state */ deferMessage(msg); break; case CMD_WIFI_CHANGE_DETECTED: if (DBG) localLog("Got wifi change detected"); reportWifiChanged((ScanResult[]) msg.obj); transitionTo(mMovingState); break; case WifiScanner.CMD_SCAN_RESULT: // nothing to do 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"); 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(CMD_WIFI_CHANGE_TIMEOUT); } }, new IntentFilter(ACTION_TIMEOUT)); } issueFullScan(); } @Override public boolean processMessage(Message msg) { if (DBG) localLog("MovingState state got " + msg); ClientInfo ci = mClients.get(msg.replyTo); switch (msg.what) { case WifiScanner.CMD_START_TRACKING_CHANGE: if (addWifiChangeHandler(ci, msg.arg2)) { replySucceeded(msg); } else { replyFailed(msg, WifiScanner.REASON_INVALID_REQUEST, "bad request"); } break; case WifiScanner.CMD_STOP_TRACKING_CHANGE: removeWifiChangeHandler(ci, msg.arg2); break; case WifiScanner.CMD_CONFIGURE_WIFI_CHANGE: 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.ELAPSED_REALTIME_WAKEUP, mClock.elapsedRealtime() + unchangedDelay, mTimeoutIntent); break; case WifiScanner.CMD_SCAN_RESULT: 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.ELAPSED_REALTIME_WAKEUP, mClock.elapsedRealtime() + MOVING_STATE_TIMEOUT_MS, mTimeoutIntent); mScanResultsPending = false; } break; case CMD_WIFI_CHANGE_DETECTED: if (DBG) localLog("Change detected"); mAlarmManager.cancel(mTimeoutIntent); reportWifiChanged((ScanResult[])msg.obj); mWifiChangeDetected = true; issueFullScan(); break; case CMD_WIFI_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; } } private 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 bssidToScanResult = new HashMap(); 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 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) 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; } private 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 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 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); } @Override public void onChangesFound(ScanResult results[]) { sendMessage(CMD_WIFI_CHANGE_DETECTED, 0, 0, results); } private void addScanRequest(ScanSettings settings) { if (DBG) localLog("Starting scans"); if (mInternalClientInfo != null) { mInternalClientInfo.sendRequestToClientHandler( WifiScanner.CMD_START_BACKGROUND_SCAN, settings, null); } } private void removeScanRequest() { if (DBG) localLog("Stopping scans"); if (mInternalClientInfo != null) { mInternalClientInfo.sendRequestToClientHandler( WifiScanner.CMD_STOP_BACKGROUND_SCAN); } } private void trackSignificantWifiChange(WifiScanner.WifiChangeSettings settings) { if (mScannerImpl != null) { mScannerImpl.untrackSignificantWifiChange(); mScannerImpl.trackSignificantWifiChange(settings, this); } } private void untrackSignificantWifiChange() { if (mScannerImpl != null) { mScannerImpl.untrackSignificantWifiChange(); } } private boolean addWifiChangeHandler(ClientInfo ci, int handler) { if (ci == null) { Log.d(TAG, "Failing wifi change request ClientInfo not found " + handler); return false; } mActiveWifiChangeHandlers.add(Pair.create(ci, handler)); // Add an internal client to make background scan requests. if (mInternalClientInfo == null) { mInternalClientInfo = new InternalClientInfo(ci.getUid(), new Messenger(this.getHandler())); mInternalClientInfo.register(); } return true; } private void removeWifiChangeHandler(ClientInfo ci, int handler) { if (ci != null) { mActiveWifiChangeHandlers.remove(Pair.create(ci, handler)); untrackSignificantWifiChangeOnEmpty(); } } private void untrackSignificantWifiChangeOnEmpty() { if (mActiveWifiChangeHandlers.isEmpty()) { if (DBG) localLog("Got Disable Wifi Change"); mCurrentBssids = null; untrackSignificantWifiChange(); // Remove the internal client when there are no more external clients. if (mInternalClientInfo != null) { mInternalClientInfo.cleanup(); mInternalClientInfo = null; } transitionTo(mDefaultState); } } private void reportWifiChanged(ScanResult[] results) { WifiScanner.ParcelableScanResults parcelableScanResults = new WifiScanner.ParcelableScanResults(results); Iterator> it = mActiveWifiChangeHandlers.iterator(); while (it.hasNext()) { Pair entry = it.next(); ClientInfo ci = entry.first; int handler = entry.second; ci.reportEvent(WifiScanner.CMD_WIFI_CHANGE_DETECTED, 0, handler, parcelableScanResults); } } private void reportWifiStabilized(ScanResult[] results) { WifiScanner.ParcelableScanResults parcelableScanResults = new WifiScanner.ParcelableScanResults(results); Iterator> it = mActiveWifiChangeHandlers.iterator(); while (it.hasNext()) { Pair entry = it.next(); ClientInfo ci = entry.first; int handler = entry.second; ci.reportEvent(WifiScanner.CMD_WIFI_CHANGES_STABILIZED, 0, handler, parcelableScanResults); } } } private static String toString(int uid, ScanSettings settings) { StringBuilder sb = new StringBuilder(); sb.append("ScanSettings[uid=").append(uid); sb.append(", period=").append(settings.periodInMs); sb.append(", report=").append(settings.reportEvents); if (settings.reportEvents == WifiScanner.REPORT_EVENT_AFTER_BUFFER_FULL && settings.numBssidsPerScan > 0 && settings.maxScansToCache > 1) { sb.append(", batch=").append(settings.maxScansToCache); sb.append(", numAP=").append(settings.numBssidsPerScan); } sb.append(", ").append(ChannelHelper.toString(settings)); sb.append("]"); return sb.toString(); } @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; } pw.println("WifiScanningService - Log Begin ----"); mLocalLog.dump(fd, pw, args); pw.println("WifiScanningService - Log End ----"); pw.println(); pw.println("clients:"); for (ClientInfo client : mClients.values()) { pw.println(" " + client); } pw.println("listeners:"); for (ClientInfo client : mClients.values()) { Collection settingsList = mBackgroundScanStateMachine.getBackgroundScanSettings(client); for (ScanSettings settings : settingsList) { pw.println(" " + toString(client.mUid, settings)); } } if (mBackgroundScheduler != null) { WifiNative.ScanSettings schedule = mBackgroundScheduler.getSchedule(); if (schedule != null) { pw.println("schedule:"); pw.println(" base period: " + schedule.base_period_ms); pw.println(" max ap per scan: " + schedule.max_ap_per_scan); pw.println(" batched scans: " + schedule.report_threshold_num_scans); pw.println(" buckets:"); for (int b = 0; b < schedule.num_buckets; b++) { WifiNative.BucketSettings bucket = schedule.buckets[b]; pw.println(" bucket " + bucket.bucket + " (" + bucket.period_ms + "ms)[" + bucket.report_events + "]: " + ChannelHelper.toString(bucket)); } } } if (mPnoScanStateMachine != null) { mPnoScanStateMachine.dump(fd, pw, args); } } void logScanRequest(String request, ClientInfo ci, int id, WorkSource workSource, ScanSettings settings, PnoSettings pnoSettings) { StringBuilder sb = new StringBuilder(); sb.append(request) .append(": ") .append((ci == null) ? "ClientInfo[unknown]" : ci.toString()) .append(",Id=") .append(id); if (workSource != null) { sb.append(",").append(workSource); } if (settings != null) { sb.append(", "); describeTo(sb, settings); } if (pnoSettings != null) { sb.append(", "); describeTo(sb, pnoSettings); } localLog(sb.toString()); } void logCallback(String callback, ClientInfo ci, int id, String extra) { StringBuilder sb = new StringBuilder(); sb.append(callback) .append(": ") .append((ci == null) ? "ClientInfo[unknown]" : ci.toString()) .append(",Id=") .append(id); if (extra != null) { sb.append(",").append(extra); } localLog(sb.toString()); } static String describeForLog(ScanData[] results) { StringBuilder sb = new StringBuilder(); sb.append("results="); for (int i = 0; i < results.length; ++i) { if (i > 0) sb.append(";"); sb.append(results[i].getResults().length); } return sb.toString(); } static String describeForLog(ScanResult[] results) { return "results=" + results.length; } static String describeTo(StringBuilder sb, ScanSettings scanSettings) { sb.append("ScanSettings { ") .append(" band:").append(scanSettings.band) .append(" period:").append(scanSettings.periodInMs) .append(" reportEvents:").append(scanSettings.reportEvents) .append(" numBssidsPerScan:").append(scanSettings.numBssidsPerScan) .append(" maxScansToCache:").append(scanSettings.maxScansToCache) .append(" channels:[ "); if (scanSettings.channels != null) { for (int i = 0; i < scanSettings.channels.length; i++) { sb.append(scanSettings.channels[i].frequency) .append(" "); } } sb.append(" ] ") .append(" } "); return sb.toString(); } static String describeTo(StringBuilder sb, PnoSettings pnoSettings) { sb.append("PnoSettings { ") .append(" min5GhzRssi:").append(pnoSettings.min5GHzRssi) .append(" min24GhzRssi:").append(pnoSettings.min24GHzRssi) .append(" initialScoreMax:").append(pnoSettings.initialScoreMax) .append(" currentConnectionBonus:").append(pnoSettings.currentConnectionBonus) .append(" sameNetworkBonus:").append(pnoSettings.sameNetworkBonus) .append(" secureBonus:").append(pnoSettings.secureBonus) .append(" band5GhzBonus:").append(pnoSettings.band5GHzBonus) .append(" isConnected:").append(pnoSettings.isConnected) .append(" networks:[ "); if (pnoSettings.networkList != null) { for (int i = 0; i < pnoSettings.networkList.length; i++) { sb.append(pnoSettings.networkList[i].ssid) .append(",") .append(pnoSettings.networkList[i].networkId) .append(" "); } } sb.append(" ] ") .append(" } "); return sb.toString(); } }