| /* |
| * 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.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.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.Looper; |
| import android.os.Message; |
| import android.os.Messenger; |
| import android.os.RemoteException; |
| import android.os.UserHandle; |
| import android.os.WorkSource; |
| import android.util.ArrayMap; |
| import android.util.LocalLog; |
| import android.util.Log; |
| import android.util.Pair; |
| |
| import com.android.internal.annotations.VisibleForTesting; |
| import com.android.internal.app.IBatteryStats; |
| import com.android.internal.util.ArrayUtils; |
| 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.FrameworkFacade; |
| import com.android.server.wifi.WifiInjector; |
| import com.android.server.wifi.WifiLog; |
| import com.android.server.wifi.WifiMetrics; |
| import com.android.server.wifi.WifiNative; |
| import com.android.server.wifi.WifiStateMachine; |
| import com.android.server.wifi.nano.WifiMetricsProto; |
| import com.android.server.wifi.scanner.ChannelHelper.ChannelCollection; |
| import com.android.server.wifi.util.WifiHandler; |
| |
| import java.io.FileDescriptor; |
| import java.io.PrintWriter; |
| import java.util.ArrayList; |
| import java.util.Arrays; |
| import java.util.Collection; |
| import java.util.Iterator; |
| import java.util.List; |
| |
| 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 WifiLog mLog; |
| |
| 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) { |
| mLog.trace("getMessenger() uid=%").c(Binder.getCallingUid()).flush(); |
| return new Messenger(mClientHandler); |
| } |
| 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<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); |
| mLog.trace("getAvailableChannels uid=%").c(Binder.getCallingUid()).flush(); |
| return b; |
| } |
| |
| private void enforceLocationHardwarePermission(int uid) { |
| mContext.enforcePermission( |
| Manifest.permission.LOCATION_HARDWARE, |
| UNKNOWN_PID, uid, |
| "LocationHardware"); |
| } |
| |
| private class ClientHandler extends WifiHandler { |
| |
| ClientHandler(String tag, Looper looper) { |
| super(tag, looper); |
| } |
| |
| @Override |
| public void handleMessage(Message msg) { |
| super.handleMessage(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 = mFrameworkFacade.makeWifiAsyncChannel(TAG); |
| 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 the CMD_GET_SCAN_RESULTS and CMD_GET_SINGLE_SCAN_RESULTS messages are |
| // sent from WifiScanner using |sendMessageSynchronously|, handle separately since |
| // the |msg.replyTo| field does not actually correspond to the Messenger that is |
| // registered for that client. |
| if (msg.what == WifiScanner.CMD_GET_SCAN_RESULTS) { |
| mBackgroundScanStateMachine.sendMessage(Message.obtain(msg)); |
| return; |
| } |
| if (msg.what == WifiScanner.CMD_GET_SINGLE_SCAN_RESULTS) { |
| mSingleScanStateMachine.sendMessage(Message.obtain(msg)); |
| return; |
| } |
| |
| ClientInfo ci = mClients.get(msg.replyTo); |
| if (ci == null) { |
| loge("Could not find client info for message " + msg.replyTo + ", msg=" + msg); |
| 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: |
| 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_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<Messenger, ClientInfo> mClients; |
| |
| private final RequestList<Void> mSingleScanListeners = new RequestList<>(); |
| |
| private ChannelHelper mChannelHelper; |
| private BackgroundScanScheduler mBackgroundScheduler; |
| private WifiNative.ScanSettings mPreviousSchedule; |
| |
| private WifiBackgroundScanStateMachine mBackgroundScanStateMachine; |
| private WifiSingleScanStateMachine mSingleScanStateMachine; |
| private WifiPnoScanStateMachine mPnoScanStateMachine; |
| private ClientHandler mClientHandler; |
| private final IBatteryStats mBatteryStats; |
| private final AlarmManager mAlarmManager; |
| private final WifiMetrics mWifiMetrics; |
| private final Clock mClock; |
| private final FrameworkFacade mFrameworkFacade; |
| |
| 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(); |
| mLog = wifiInjector.makeLog(TAG); |
| mFrameworkFacade = wifiInjector.getFrameworkFacade(); |
| mPreviousSchedule = null; |
| } |
| |
| public void startService() { |
| mClientHandler = new ClientHandler(TAG, mLooper); |
| mBackgroundScanStateMachine = new WifiBackgroundScanStateMachine(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(); |
| mSingleScanStateMachine.start(); |
| mPnoScanStateMachine.start(); |
| } |
| |
| /** |
| * Provide a way for unit tests to set valid log object in the WifiHandler |
| * @param log WifiLog object to assign to the clientHandler |
| */ |
| @VisibleForTesting |
| public void setWifiHandlerLogForTest(WifiLog log) { |
| mClientHandler.setWifiLog(log); |
| } |
| |
| 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<T> { |
| 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<T> extends ArrayList<RequestInfo<T>> { |
| void addRequest(ClientInfo ci, int handler, WorkSource reqworkSource, T settings) { |
| add(new RequestInfo<T>(ci, handler, reqworkSource, settings)); |
| } |
| |
| T removeRequest(ClientInfo ci, int handlerId) { |
| T removed = null; |
| Iterator<RequestInfo<T>> iter = iterator(); |
| while (iter.hasNext()) { |
| RequestInfo<T> entry = iter.next(); |
| if (entry.clientInfo == ci && entry.handlerId == handlerId) { |
| removed = entry.settings; |
| iter.remove(); |
| } |
| } |
| return removed; |
| } |
| |
| Collection<T> getAllSettings() { |
| ArrayList<T> settingsList = new ArrayList<>(); |
| Iterator<RequestInfo<T>> iter = iterator(); |
| while (iter.hasNext()) { |
| RequestInfo<T> entry = iter.next(); |
| settingsList.add(entry.settings); |
| } |
| return settingsList; |
| } |
| |
| Collection<T> getAllSettingsForClient(ClientInfo ci) { |
| ArrayList<T> settingsList = new ArrayList<>(); |
| Iterator<RequestInfo<T>> iter = iterator(); |
| while (iter.hasNext()) { |
| RequestInfo<T> entry = iter.next(); |
| if (entry.clientInfo == ci) { |
| settingsList.add(entry.settings); |
| } |
| } |
| return settingsList; |
| } |
| |
| void removeAllForClient(ClientInfo ci) { |
| Iterator<RequestInfo<T>> iter = iterator(); |
| while (iter.hasNext()) { |
| RequestInfo<T> entry = iter.next(); |
| if (entry.clientInfo == ci) { |
| iter.remove(); |
| } |
| } |
| } |
| |
| WorkSource createMergedWorkSource() { |
| WorkSource mergedSource = new WorkSource(); |
| for (RequestInfo<T> 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 { |
| /** |
| * Maximum age of results that we return from our cache via |
| * {@link WifiScanner#getScanResults()}. |
| * This is currently set to 3 minutes to restore parity with the wpa_supplicant's scan |
| * result cache expiration policy. (See b/62253332 for details) |
| */ |
| @VisibleForTesting |
| public static final int CACHED_SCAN_RESULTS_MAX_AGE_IN_MILLIS = 180 * 1000; |
| |
| 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<ScanSettings> mActiveScans = new RequestList<>(); |
| private RequestList<ScanSettings> mPendingScans = new RequestList<>(); |
| |
| // Scan results cached from the last full single scan request. |
| private final List<ScanResult> mCachedScanResults = new ArrayList<>(); |
| |
| 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; |
| case WifiScanner.CMD_GET_SINGLE_SCAN_RESULTS: |
| msg.obj = new WifiScanner.ParcelableScanResults( |
| filterCachedScanResultsByAge()); |
| replySucceeded(msg); |
| return HANDLED; |
| default: |
| return NOT_HANDLED; |
| } |
| } |
| |
| /** |
| * Filter out any scan results that are older than |
| * {@link #CACHED_SCAN_RESULTS_MAX_AGE_IN_MILLIS}. |
| * |
| * @return Filtered list of scan results. |
| */ |
| private ScanResult[] filterCachedScanResultsByAge() { |
| // Using ScanResult.timestamp here to ensure that we use the same fields as |
| // WificondScannerImpl for filtering stale results. |
| long currentTimeInMillis = mClock.getElapsedSinceBootMillis(); |
| return mCachedScanResults.stream() |
| .filter(scanResult |
| -> ((currentTimeInMillis - (scanResult.timestamp / 1000)) |
| < CACHED_SCAN_RESULTS_MAX_AGE_IN_MILLIS)) |
| .toArray(ScanResult[]::new); |
| } |
| } |
| |
| /** |
| * 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() { |
| // clear scan results when scan mode is not active |
| mCachedScanResults.clear(); |
| |
| 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()); |
| sendScanResultBroadcast(false); |
| 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 (!ArrayUtils.isEmpty(settings.hiddenNetworks)) { |
| if (ArrayUtils.isEmpty(mActiveScanSettings.hiddenNetworks)) { |
| return false; |
| } |
| List<WifiNative.HiddenNetwork> activeHiddenNetworks = new ArrayList<>(); |
| for (WifiNative.HiddenNetwork hiddenNetwork : mActiveScanSettings.hiddenNetworks) { |
| activeHiddenNetworks.add(hiddenNetwork); |
| } |
| for (ScanSettings.HiddenNetwork hiddenNetwork : settings.hiddenNetworks) { |
| WifiNative.HiddenNetwork nativeHiddenNetwork = new WifiNative.HiddenNetwork(); |
| nativeHiddenNetwork.ssid = hiddenNetwork.ssid; |
| if (!activeHiddenNetworks.contains(nativeHiddenNetwork)) { |
| 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(); |
| List<WifiNative.HiddenNetwork> hiddenNetworkList = new ArrayList<>(); |
| for (RequestInfo<ScanSettings> entry : mPendingScans) { |
| channels.addChannels(entry.settings); |
| if (entry.settings.hiddenNetworks != null) { |
| for (int i = 0; i < entry.settings.hiddenNetworks.length; i++) { |
| WifiNative.HiddenNetwork hiddenNetwork = new WifiNative.HiddenNetwork(); |
| hiddenNetwork.ssid = entry.settings.hiddenNetworks[i].ssid; |
| hiddenNetworkList.add(hiddenNetwork); |
| } |
| } |
| if ((entry.settings.reportEvents & WifiScanner.REPORT_EVENT_FULL_SCAN_RESULT) |
| != 0) { |
| bucketSettings.report_events |= WifiScanner.REPORT_EVENT_FULL_SCAN_RESULT; |
| } |
| } |
| if (hiddenNetworkList.size() > 0) { |
| settings.hiddenNetworks = new WifiNative.HiddenNetwork[hiddenNetworkList.size()]; |
| int numHiddenNetworks = 0; |
| for (WifiNative.HiddenNetwork hiddenNetwork : hiddenNetworkList) { |
| settings.hiddenNetworks[numHiddenNetworks++] = hiddenNetwork; |
| } |
| } |
| |
| 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<ScanSettings> 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<ScanSettings> entry : mActiveScans) { |
| if (ScanScheduleUtil.shouldReportFullScanResultForSettings(mChannelHelper, |
| result, bucketsScanned, entry.settings, -1)) { |
| entry.reportEvent(WifiScanner.CMD_FULL_SCAN_RESULT, 0, result); |
| } |
| } |
| |
| for (RequestInfo<Void> entry : mSingleScanListeners) { |
| entry.reportEvent(WifiScanner.CMD_FULL_SCAN_RESULT, 0, result); |
| } |
| } |
| |
| private void sendScanResultBroadcast(boolean scanSucceeded) { |
| Intent intent = new Intent(WifiManager.SCAN_RESULTS_AVAILABLE_ACTION); |
| intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT); |
| intent.putExtra(WifiManager.EXTRA_RESULTS_UPDATED, scanSucceeded); |
| mContext.sendBroadcastAsUser(intent, UserHandle.ALL); |
| } |
| |
| 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<ScanSettings> 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<Void> entry : mSingleScanListeners) { |
| logCallback("singleScanResults", entry.clientInfo, entry.handlerId, |
| describeForLog(allResults)); |
| entry.reportEvent(WifiScanner.CMD_SCAN_RESULT, 0, parcelableAllResults); |
| } |
| |
| if (results.isAllChannelsScanned()) { |
| mCachedScanResults.clear(); |
| mCachedScanResults.addAll(Arrays.asList(results.getResults())); |
| sendScanResultBroadcast(true); |
| } |
| } |
| |
| List<ScanResult> getCachedScanResultsAsList() { |
| return mCachedScanResults; |
| } |
| } |
| |
| class WifiBackgroundScanStateMachine extends StateMachine |
| implements WifiNative.ScanEventHandler { |
| |
| private final DefaultState mDefaultState = new DefaultState(); |
| private final StartedState mStartedState = new StartedState(); |
| private final PausedState mPausedState = new PausedState(); |
| |
| private final RequestList<ScanSettings> mActiveBackgroundScans = 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<ScanSettings> getBackgroundScanSettings(ClientInfo ci) { |
| return mActiveBackgroundScans.getAllSettingsForClient(ci); |
| } |
| |
| public void removeBackgroundScanSettings(ClientInfo ci) { |
| mActiveBackgroundScans.removeAllForClient(ci); |
| updateSchedule(); |
| } |
| |
| @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); |
| } |
| |
| class DefaultState extends State { |
| @Override |
| public void enter() { |
| if (DBG) localLog("DefaultState"); |
| mActiveBackgroundScans.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; |
| } |
| if (capabilities.max_scan_buckets <= 0) { |
| loge("invalid max buckets in scan capabilities " |
| + capabilities.max_scan_buckets); |
| 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_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"); |
| 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 CMD_SCAN_RESULTS_AVAILABLE: |
| reportScanResults(mScannerImpl.getLatestBatchedScanResults(true)); |
| break; |
| case CMD_FULL_SCAN_RESULTS: |
| reportFullScanResult((ScanResult) msg.obj, /* bucketsScanned */ msg.arg2); |
| 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<ScanSettings> 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<ScanSettings> 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) { |
| if (results == null) { |
| Log.d(TAG,"The results is null, nothing to report."); |
| return; |
| } |
| for (ScanData result : results) { |
| if (result != null && result.getResults() != null) { |
| if (result.getResults().length > 0) { |
| mWifiMetrics.incrementNonEmptyScanResultCount(); |
| } else { |
| mWifiMetrics.incrementEmptyScanResultCount(); |
| } |
| } |
| } |
| for (RequestInfo<ScanSettings> 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<ScanSettings> 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(); |
| } |
| } |
| |
| /** |
| * 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 wificond 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 wificond 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<Pair<PnoSettings, ScanSettings>> mActivePnoScans = |
| new RequestList<>(); |
| |
| WifiPnoScanStateMachine(Looper looper) { |
| super("WifiPnoScanStateMachine", looper); |
| |
| setLogRecSize(256); |
| 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<ScanResult> 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 convertSettingsToPnoNative(ScanSettings scanSettings, |
| PnoSettings pnoSettings) { |
| WifiNative.PnoSettings nativePnoSetting = new WifiNative.PnoSettings(); |
| nativePnoSetting.periodInMs = scanSettings.periodInMs; |
| 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].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<PnoSettings, ScanSettings> 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<PnoSettings, ScanSettings> removePnoScanRequest(ClientInfo ci, int handler) { |
| Pair<PnoSettings, ScanSettings> 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 = |
| convertSettingsToPnoNative(scanSettings, 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<PnoSettings, ScanSettings> 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<PnoSettings, ScanSettings> 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<Pair<PnoSettings, ScanSettings>> 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<Pair<PnoSettings, ScanSettings>> 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); |
| 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<ScanSettings> 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; |
| 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; |
| if (msg.obj != null) { |
| reply.obj = msg.obj; |
| } |
| try { |
| msg.replyTo.send(reply); |
| mLog.trace("replySucceeded recvdMessage=%").c(msg.what).flush(); |
| } 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); |
| mLog.trace("replyFailed recvdMessage=% reason=%") |
| .c(msg.what) |
| .c(reason) |
| .flush(); |
| } catch (RemoteException e) { |
| // There's not much we can do if reply can't be sent! |
| } |
| } else { |
| // locally generated message; doesn't need a reply! |
| } |
| } |
| |
| private static 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<ScanSettings> 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); |
| } |
| pw.println(); |
| |
| if (mSingleScanStateMachine != null) { |
| mSingleScanStateMachine.dump(fd, pw, args); |
| pw.println(); |
| pw.println("Latest scan results:"); |
| List<ScanResult> scanResults = mSingleScanStateMachine.getCachedScanResultsAsList(); |
| long nowMs = mClock.getElapsedSinceBootMillis(); |
| if (scanResults != null && scanResults.size() != 0) { |
| pw.println(" BSSID Frequency RSSI Age(sec) SSID " |
| + " Flags"); |
| for (ScanResult r : scanResults) { |
| long timeStampMs = r.timestamp / 1000; |
| String age; |
| if (timeStampMs <= 0) { |
| age = "___?___"; |
| } else if (nowMs < timeStampMs) { |
| age = " 0.000"; |
| } else if (timeStampMs < nowMs - 1000000) { |
| age = ">1000.0"; |
| } else { |
| age = String.format("%3.3f", (nowMs - timeStampMs) / 1000.0); |
| } |
| String ssid = r.SSID == null ? "" : r.SSID; |
| pw.printf(" %17s %9d %5d %7s %-32s %s\n", |
| r.BSSID, |
| r.frequency, |
| r.level, |
| age, |
| String.format("%1.32s", ssid), |
| r.capabilities); |
| } |
| } |
| pw.println(); |
| } |
| if (mScannerImpl != null) { |
| mScannerImpl.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(","); |
| } |
| } |
| sb.append(" ] ") |
| .append(" } "); |
| return sb.toString(); |
| } |
| } |