| /* |
| * Copyright (C) 2016 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| package com.android.server.wifi; |
| |
| import static com.android.server.wifi.ActiveModeManager.ClientInternetConnectivityRole; |
| import static com.android.server.wifi.ClientModeImpl.WIFI_WORK_SOURCE; |
| |
| import android.annotation.NonNull; |
| import android.app.AlarmManager; |
| import android.content.BroadcastReceiver; |
| import android.content.Context; |
| import android.content.Intent; |
| import android.content.IntentFilter; |
| import android.net.MacAddress; |
| import android.net.wifi.ScanResult; |
| import android.net.wifi.SupplicantState; |
| import android.net.wifi.WifiConfiguration; |
| import android.net.wifi.WifiInfo; |
| import android.net.wifi.WifiManager; |
| import android.net.wifi.WifiManager.DeviceMobilityState; |
| import android.net.wifi.WifiNetworkSuggestion; |
| import android.net.wifi.WifiScanner; |
| import android.net.wifi.WifiScanner.PnoSettings; |
| import android.net.wifi.WifiScanner.ScanSettings; |
| import android.net.wifi.hotspot2.PasspointConfiguration; |
| import android.os.Handler; |
| import android.os.HandlerExecutor; |
| import android.os.PowerManager; |
| import android.os.Process; |
| import android.os.WorkSource; |
| import android.util.ArrayMap; |
| import android.util.ArraySet; |
| import android.util.LocalLog; |
| import android.util.Log; |
| |
| import com.android.internal.annotations.GuardedBy; |
| import com.android.internal.annotations.VisibleForTesting; |
| import com.android.server.wifi.hotspot2.PasspointManager; |
| import com.android.server.wifi.util.ScanResultUtil; |
| import com.android.wifi.resources.R; |
| |
| import java.io.FileDescriptor; |
| import java.io.PrintWriter; |
| import java.util.ArrayList; |
| import java.util.Arrays; |
| import java.util.Collections; |
| import java.util.HashSet; |
| import java.util.Iterator; |
| import java.util.LinkedList; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.Objects; |
| import java.util.Set; |
| import java.util.stream.Collectors; |
| |
| /** |
| * This class manages all the connectivity related scanning activities. |
| * |
| * When the screen is turned on or off, WiFi is connected or disconnected, |
| * or on-demand, a scan is initiatiated and the scan results are passed |
| * to WifiNetworkSelector for it to make a recommendation on which network |
| * to connect to. |
| */ |
| public class WifiConnectivityManager { |
| public static final String WATCHDOG_TIMER_TAG = |
| "WifiConnectivityManager Schedule Watchdog Timer"; |
| public static final String PERIODIC_SCAN_TIMER_TAG = |
| "WifiConnectivityManager Schedule Periodic Scan Timer"; |
| public static final String RESTART_SINGLE_SCAN_TIMER_TAG = |
| "WifiConnectivityManager Restart Single Scan"; |
| public static final String RESTART_CONNECTIVITY_SCAN_TIMER_TAG = |
| "WifiConnectivityManager Restart Scan"; |
| public static final String DELAYED_PARTIAL_SCAN_TIMER_TAG = |
| "WifiConnectivityManager Schedule Delayed Partial Scan Timer"; |
| |
| private static final long RESET_TIME_STAMP = Long.MIN_VALUE; |
| // Constants to indicate whether a scan should start immediately or |
| // it should comply to the minimum scan interval rule. |
| private static final boolean SCAN_IMMEDIATELY = true; |
| private static final boolean SCAN_ON_SCHEDULE = false; |
| |
| // PNO scan interval in milli-seconds. This is the scan |
| // performed when screen is off and connected. |
| private static final int CONNECTED_PNO_SCAN_INTERVAL_MS = 160 * 1000; // 160 seconds |
| // When a network is found by PNO scan but gets rejected by Wifi Network Selector due |
| // to its low RSSI value, scan will be reschduled in an exponential back off manner. |
| private static final int LOW_RSSI_NETWORK_RETRY_START_DELAY_MS = 20 * 1000; // 20 seconds |
| private static final int LOW_RSSI_NETWORK_RETRY_MAX_DELAY_MS = 80 * 1000; // 80 seconds |
| // Maximum number of retries when starting a scan failed |
| @VisibleForTesting |
| public static final int MAX_SCAN_RESTART_ALLOWED = 5; |
| // Number of milli-seconds to delay before retry starting |
| // a previously failed scan |
| private static final int RESTART_SCAN_DELAY_MS = 2 * 1000; // 2 seconds |
| // When in disconnected mode, a watchdog timer will be fired |
| // every WATCHDOG_INTERVAL_MS to start a single scan. This is |
| // to prevent caveat from things like PNO scan. |
| private static final int WATCHDOG_INTERVAL_MS = 20 * 60 * 1000; // 20 minutes |
| // Restricted channel list age out value. |
| private static final long CHANNEL_LIST_AGE_MS = 60 * 60 * 1000; // 1 hour |
| // This is the time interval for the connection attempt rate calculation. Connection attempt |
| // timestamps beyond this interval is evicted from the list. |
| public static final int MAX_CONNECTION_ATTEMPTS_TIME_INTERVAL_MS = 4 * 60 * 1000; // 4 mins |
| // Max number of connection attempts in the above time interval. |
| public static final int MAX_CONNECTION_ATTEMPTS_RATE = 6; |
| private static final int TEMP_BSSID_BLOCK_DURATION = 10 * 1000; // 10 seconds |
| // Maximum age of frequencies last seen to be included in pno scans. (30 days) |
| private static final long MAX_PNO_SCAN_FREQUENCY_AGE_MS = (long) 1000 * 3600 * 24 * 30; |
| private static final int POWER_SAVE_SCAN_INTERVAL_MULTIPLIER = 2; |
| // ClientModeManager has a bunch of states. From the |
| // WifiConnectivityManager's perspective it only cares |
| // if it is in Connected state, Disconnected state or in |
| // transition between these two states. |
| public static final int WIFI_STATE_UNKNOWN = 0; |
| public static final int WIFI_STATE_CONNECTED = 1; |
| public static final int WIFI_STATE_DISCONNECTED = 2; |
| public static final int WIFI_STATE_TRANSITIONING = 3; |
| |
| // Initial scan state, used to manage performing partial scans in initial scans |
| // Initial scans are the first scan after enabling Wifi or turning on screen when disconnected |
| private static final int INITIAL_SCAN_STATE_START = 0; |
| private static final int INITIAL_SCAN_STATE_AWAITING_RESPONSE = 1; |
| private static final int INITIAL_SCAN_STATE_COMPLETE = 2; |
| |
| // Log tag for this class |
| private static final String TAG = "WifiConnectivityManager"; |
| private static final String ALL_SINGLE_SCAN_LISTENER = "AllSingleScanListener"; |
| private static final String PNO_SCAN_LISTENER = "PnoScanListener"; |
| |
| private final Context mContext; |
| private final WifiConfigManager mConfigManager; |
| private final WifiNetworkSuggestionsManager mWifiNetworkSuggestionsManager; |
| private final WifiInfo mWifiInfo; |
| private final WifiConnectivityHelper mConnectivityHelper; |
| private final WifiNetworkSelector mNetworkSelector; |
| private final WifiLastResortWatchdog mWifiLastResortWatchdog; |
| private final OpenNetworkNotifier mOpenNetworkNotifier; |
| private final WifiMetrics mWifiMetrics; |
| private final AlarmManager mAlarmManager; |
| private final Handler mEventHandler; |
| private final Clock mClock; |
| private final ScoringParams mScoringParams; |
| private final LocalLog mLocalLog; |
| private final LinkedList<Long> mConnectionAttemptTimeStamps = new LinkedList<>(); |
| private final BssidBlocklistMonitor mBssidBlocklistMonitor; |
| private final PasspointManager mPasspointManager; |
| private final WifiScoreCard mWifiScoreCard; |
| private final WifiChannelUtilization mWifiChannelUtilization; |
| private final PowerManager mPowerManager; |
| private final DeviceConfigFacade mDeviceConfigFacade; |
| private final ActiveModeWarden mActiveModeWarden; |
| |
| private WifiScanner mScanner; |
| private boolean mDbg = false; |
| private boolean mVerboseLoggingEnabled = false; |
| private boolean mWifiEnabled = false; |
| private boolean mAutoJoinEnabled = false; // disabled by default, enabled by external triggers |
| private boolean mRunning = false; |
| private boolean mScreenOn = false; |
| private int mWifiState = WIFI_STATE_UNKNOWN; |
| private int mInitialScanState = INITIAL_SCAN_STATE_COMPLETE; |
| private boolean mAutoJoinEnabledExternal = true; // enabled by default |
| private boolean mUntrustedConnectionAllowed = false; |
| private boolean mTrustedConnectionAllowed = false; |
| private boolean mSpecificNetworkRequestInProgress = false; |
| private int mScanRestartCount = 0; |
| private int mSingleScanRestartCount = 0; |
| private int mTotalConnectivityAttemptsRateLimited = 0; |
| private String mLastConnectionAttemptBssid = null; |
| private long mLastPeriodicSingleScanTimeStamp = RESET_TIME_STAMP; |
| private long mLastNetworkSelectionTimeStamp = RESET_TIME_STAMP; |
| private boolean mPnoScanStarted = false; |
| private boolean mPeriodicScanTimerSet = false; |
| private boolean mDelayedPartialScanTimerSet = false; |
| |
| // Used for Initial Scan metrics |
| private boolean mFailedInitialPartialScan = false; |
| private int mInitialPartialScanChannelCount; |
| |
| // Device configs |
| private boolean mWaitForFullBandScanResults = false; |
| |
| // Scanning Schedules |
| // Default schedule used in case of invalid configuration |
| private static final int[] DEFAULT_SCANNING_SCHEDULE_SEC = {20, 40, 80, 160}; |
| private int[] mConnectedSingleScanScheduleSec; |
| private int[] mDisconnectedSingleScanScheduleSec; |
| private int[] mConnectedSingleSavedNetworkSingleScanScheduleSec; |
| private List<WifiCandidates.Candidate> mLatestCandidates = null; |
| private long mLatestCandidatesTimestampMs = 0; |
| |
| private final Object mLock = new Object(); |
| |
| @GuardedBy("mLock") |
| private int[] mCurrentSingleScanScheduleSec; |
| |
| private int mCurrentSingleScanScheduleIndex; |
| // Cached WifiCandidates used in high mobility state to avoid connecting to APs that are |
| // moving relative to the user. |
| private CachedWifiCandidates mCachedWifiCandidates = null; |
| private @DeviceMobilityState int mDeviceMobilityState = |
| WifiManager.DEVICE_MOBILITY_STATE_UNKNOWN; |
| |
| // Set of active client mode managers. This would be more than 1 only in case of |
| // STA + STA use-cases. |
| private final ArraySet<ClientModeManager> mClientModeManagers = new ArraySet<>(); |
| |
| // A helper to log debugging information in the local log buffer, which can |
| // be retrieved in bugreport. |
| private void localLog(String log) { |
| mLocalLog.log(log); |
| if (mVerboseLoggingEnabled) Log.v(TAG, log); |
| } |
| |
| /** |
| * Enable verbose logging for WifiConnectivityManager. |
| */ |
| public void enableVerboseLogging(boolean verbose) { |
| mVerboseLoggingEnabled = verbose; |
| } |
| |
| // A periodic/PNO scan will be rescheduled up to MAX_SCAN_RESTART_ALLOWED times |
| // if the start scan command failed. A timer is used here to make it a deferred retry. |
| private final AlarmManager.OnAlarmListener mRestartScanListener = |
| new AlarmManager.OnAlarmListener() { |
| public void onAlarm() { |
| startConnectivityScan(SCAN_IMMEDIATELY); |
| } |
| }; |
| |
| // A single scan will be rescheduled up to MAX_SCAN_RESTART_ALLOWED times |
| // if the start scan command failed. An timer is used here to make it a deferred retry. |
| private class RestartSingleScanListener implements AlarmManager.OnAlarmListener { |
| private final boolean mIsFullBandScan; |
| |
| RestartSingleScanListener(boolean isFullBandScan) { |
| mIsFullBandScan = isFullBandScan; |
| } |
| |
| @Override |
| public void onAlarm() { |
| startSingleScan(mIsFullBandScan, WIFI_WORK_SOURCE); |
| } |
| } |
| |
| // As a watchdog mechanism, a single scan will be scheduled every WATCHDOG_INTERVAL_MS |
| // if it is in the WIFI_STATE_DISCONNECTED state. |
| private final AlarmManager.OnAlarmListener mWatchdogListener = |
| new AlarmManager.OnAlarmListener() { |
| public void onAlarm() { |
| watchdogHandler(); |
| } |
| }; |
| |
| // Due to b/28020168, timer based single scan will be scheduled |
| // to provide periodic scan in an exponential backoff fashion. |
| private final AlarmManager.OnAlarmListener mPeriodicScanTimerListener = |
| new AlarmManager.OnAlarmListener() { |
| public void onAlarm() { |
| periodicScanTimerHandler(); |
| } |
| }; |
| |
| private final AlarmManager.OnAlarmListener mDelayedPartialScanTimerListener = |
| new AlarmManager.OnAlarmListener() { |
| public void onAlarm() { |
| if (mCachedWifiCandidates == null |
| || mCachedWifiCandidates.frequencies == null |
| || mCachedWifiCandidates.frequencies.size() == 0) { |
| return; |
| } |
| ScanSettings settings = new ScanSettings(); |
| settings.type = WifiScanner.SCAN_TYPE_HIGH_ACCURACY; |
| settings.band = getScanBand(false); |
| settings.reportEvents = WifiScanner.REPORT_EVENT_FULL_SCAN_RESULT |
| | WifiScanner.REPORT_EVENT_AFTER_EACH_SCAN; |
| settings.numBssidsPerScan = 0; |
| int index = 0; |
| settings.channels = |
| new WifiScanner.ChannelSpec[mCachedWifiCandidates.frequencies.size()]; |
| for (Integer freq : mCachedWifiCandidates.frequencies) { |
| settings.channels[index++] = new WifiScanner.ChannelSpec(freq); |
| } |
| SingleScanListener singleScanListener = new SingleScanListener(false); |
| mScanner.startScan(settings, new HandlerExecutor(mEventHandler), |
| singleScanListener, WIFI_WORK_SOURCE); |
| mWifiMetrics.incrementConnectivityOneshotScanCount(); |
| } |
| }; |
| |
| /** |
| * Handles 'onResult' callbacks for the Periodic, Single & Pno ScanListener. |
| * Executes selection of potential network candidates, initiation of connection attempt to that |
| * network. |
| * |
| * @return true - if a candidate is selected by WifiNetworkSelector |
| * false - if no candidate is selected by WifiNetworkSelector |
| */ |
| private boolean handleScanResults(List<ScanDetail> scanDetails, String listenerName, |
| boolean isFullScan) { |
| ClientModeManager clientModeManager = getClientModeManager(); |
| mWifiChannelUtilization.refreshChannelStatsAndChannelUtilization( |
| clientModeManager.getWifiLinkLayerStats(), |
| WifiChannelUtilization.UNKNOWN_FREQ); |
| |
| updateUserDisabledList(scanDetails); |
| |
| // Check if any blocklisted BSSIDs can be freed. |
| mBssidBlocklistMonitor.tryEnablingBlockedBssids(scanDetails); |
| Set<String> bssidBlocklist = mBssidBlocklistMonitor.updateAndGetBssidBlocklist(); |
| |
| if (clientModeManager.isSupplicantTransientState()) { |
| localLog(listenerName |
| + " onResults: No network selection because supplicantTransientState is " |
| + clientModeManager.isSupplicantTransientState()); |
| return false; |
| } |
| |
| localLog(listenerName + " onResults: start network selection"); |
| |
| List<WifiCandidates.Candidate> candidates = mNetworkSelector.getCandidatesFromScan( |
| scanDetails, bssidBlocklist, mWifiInfo, clientModeManager.isConnected(), |
| clientModeManager.isDisconnected(), mUntrustedConnectionAllowed); |
| mLatestCandidates = candidates; |
| mLatestCandidatesTimestampMs = mClock.getElapsedSinceBootMillis(); |
| |
| if (mDeviceMobilityState == WifiManager.DEVICE_MOBILITY_STATE_HIGH_MVMT |
| && mContext.getResources().getBoolean( |
| R.bool.config_wifiHighMovementNetworkSelectionOptimizationEnabled)) { |
| candidates = filterCandidatesHighMovement(candidates, listenerName, isFullScan); |
| } |
| |
| WifiConfiguration candidate = mNetworkSelector.selectNetwork(candidates); |
| mLastNetworkSelectionTimeStamp = mClock.getElapsedSinceBootMillis(); |
| mWifiLastResortWatchdog.updateAvailableNetworks( |
| mNetworkSelector.getConnectableScanDetails()); |
| mWifiMetrics.countScanResults(scanDetails); |
| if (candidate != null) { |
| localLog(listenerName + ": WNS candidate-" + candidate.SSID); |
| connectToNetwork(candidate); |
| return true; |
| } else { |
| if (mWifiState == WIFI_STATE_DISCONNECTED) { |
| mOpenNetworkNotifier.handleScanResults( |
| mNetworkSelector.getFilteredScanDetailsForOpenUnsavedNetworks()); |
| } |
| return false; |
| } |
| } |
| |
| private List<WifiCandidates.Candidate> filterCandidatesHighMovement( |
| List<WifiCandidates.Candidate> candidates, String listenerName, boolean isFullScan) { |
| boolean isNotPartialScan = isFullScan || listenerName.equals(PNO_SCAN_LISTENER); |
| if (candidates == null || candidates.isEmpty()) { |
| // No connectable networks nearby or network selection is unnecessary |
| if (isNotPartialScan) { |
| mCachedWifiCandidates = new CachedWifiCandidates(mClock.getElapsedSinceBootMillis(), |
| null); |
| } |
| return null; |
| } |
| |
| long minimumTimeBetweenScansMs = mContext.getResources().getInteger( |
| R.integer.config_wifiHighMovementNetworkSelectionOptimizationScanDelayMs); |
| if (mCachedWifiCandidates != null && mCachedWifiCandidates.candidateRssiMap != null) { |
| // cached candidates are too recent, wait for next scan |
| if (mClock.getElapsedSinceBootMillis() - mCachedWifiCandidates.timeSinceBootMs |
| < minimumTimeBetweenScansMs) { |
| mWifiMetrics.incrementNumHighMovementConnectionSkipped(); |
| return null; |
| } |
| |
| int rssiDelta = mContext.getResources().getInteger(R.integer |
| .config_wifiHighMovementNetworkSelectionOptimizationRssiDelta); |
| List<WifiCandidates.Candidate> filteredCandidates = candidates.stream().filter( |
| item -> mCachedWifiCandidates.candidateRssiMap.containsKey(item.getKey()) |
| && Math.abs(mCachedWifiCandidates.candidateRssiMap.get(item.getKey()) |
| - item.getScanRssi()) < rssiDelta) |
| .collect(Collectors.toList()); |
| |
| if (!filteredCandidates.isEmpty()) { |
| if (isNotPartialScan) { |
| mCachedWifiCandidates = |
| new CachedWifiCandidates(mClock.getElapsedSinceBootMillis(), |
| candidates); |
| } |
| mWifiMetrics.incrementNumHighMovementConnectionStarted(); |
| return filteredCandidates; |
| } |
| } |
| |
| // Either no cached candidates, or all candidates got filtered out. |
| // Update the cached candidates here and schedule a delayed partial scan. |
| if (isNotPartialScan) { |
| mCachedWifiCandidates = new CachedWifiCandidates(mClock.getElapsedSinceBootMillis(), |
| candidates); |
| localLog("Found " + candidates.size() + " candidates at high mobility state. " |
| + "Re-doing scan to confirm network quality."); |
| scheduleDelayedPartialScan(minimumTimeBetweenScansMs); |
| } |
| mWifiMetrics.incrementNumHighMovementConnectionSkipped(); |
| return null; |
| } |
| |
| private void updateUserDisabledList(List<ScanDetail> scanDetails) { |
| List<String> results = new ArrayList<>(); |
| List<ScanResult> passpointAp = new ArrayList<>(); |
| for (ScanDetail scanDetail : scanDetails) { |
| results.add(ScanResultUtil.createQuotedSSID(scanDetail.getScanResult().SSID)); |
| if (!scanDetail.getScanResult().isPasspointNetwork()) { |
| continue; |
| } |
| passpointAp.add(scanDetail.getScanResult()); |
| } |
| if (!passpointAp.isEmpty()) { |
| results.addAll(mPasspointManager |
| .getAllMatchingPasspointProfilesForScanResults(passpointAp).keySet()); |
| } |
| mConfigManager.updateUserDisabledList(results); |
| } |
| |
| private class CachedWifiCandidates { |
| public final long timeSinceBootMs; |
| public final Map<WifiCandidates.Key, Integer> candidateRssiMap; |
| public final Set<Integer> frequencies; |
| |
| CachedWifiCandidates(long timeSinceBootMs, List<WifiCandidates.Candidate> candidates) { |
| this.timeSinceBootMs = timeSinceBootMs; |
| if (candidates == null) { |
| this.candidateRssiMap = null; |
| this.frequencies = null; |
| } else { |
| this.candidateRssiMap = new ArrayMap<WifiCandidates.Key, Integer>(); |
| this.frequencies = new HashSet<Integer>(); |
| for (WifiCandidates.Candidate c : candidates) { |
| candidateRssiMap.put(c.getKey(), c.getScanRssi()); |
| frequencies.add(c.getFrequency()); |
| } |
| } |
| } |
| } |
| |
| // All single scan results listener. |
| // |
| // Note: This is the listener for all the available single scan results, |
| // including the ones initiated by WifiConnectivityManager and |
| // other modules. |
| private class AllSingleScanListener implements WifiScanner.ScanListener { |
| private List<ScanDetail> mScanDetails = new ArrayList<ScanDetail>(); |
| private int mNumScanResultsIgnoredDueToSingleRadioChain = 0; |
| |
| public void clearScanDetails() { |
| mScanDetails.clear(); |
| mNumScanResultsIgnoredDueToSingleRadioChain = 0; |
| } |
| |
| @Override |
| public void onSuccess() { |
| } |
| |
| @Override |
| public void onFailure(int reason, String description) { |
| localLog("registerScanListener onFailure:" |
| + " reason: " + reason + " description: " + description); |
| } |
| |
| @Override |
| public void onPeriodChanged(int periodInMs) { |
| } |
| |
| @Override |
| public void onResults(WifiScanner.ScanData[] results) { |
| if (!mWifiEnabled || !mAutoJoinEnabled) { |
| clearScanDetails(); |
| mWaitForFullBandScanResults = false; |
| return; |
| } |
| |
| // We treat any full band scans (with DFS or not) as "full". |
| boolean isFullBandScanResults = false; |
| if (results != null && results.length > 0) { |
| isFullBandScanResults = |
| WifiScanner.isFullBandScan(results[0].getBandScanned(), true); |
| } |
| // Full band scan results only. |
| if (mWaitForFullBandScanResults) { |
| if (!isFullBandScanResults) { |
| localLog("AllSingleScanListener waiting for full band scan results."); |
| clearScanDetails(); |
| return; |
| } else { |
| mWaitForFullBandScanResults = false; |
| } |
| } |
| if (results != null && results.length > 0) { |
| mWifiMetrics.incrementAvailableNetworksHistograms(mScanDetails, |
| isFullBandScanResults); |
| } |
| if (mNumScanResultsIgnoredDueToSingleRadioChain > 0) { |
| Log.i(TAG, "Number of scan results ignored due to single radio chain scan: " |
| + mNumScanResultsIgnoredDueToSingleRadioChain); |
| } |
| boolean wasConnectAttempted = handleScanResults(mScanDetails, |
| ALL_SINGLE_SCAN_LISTENER, isFullBandScanResults); |
| clearScanDetails(); |
| |
| // Update metrics to see if a single scan detected a valid network |
| // while PNO scan didn't. |
| // Note: We don't update the background scan metrics any more as it is |
| // not in use. |
| if (mPnoScanStarted) { |
| if (wasConnectAttempted) { |
| mWifiMetrics.incrementNumConnectivityWatchdogPnoBad(); |
| } else { |
| mWifiMetrics.incrementNumConnectivityWatchdogPnoGood(); |
| } |
| } |
| |
| // Check if we are in the middle of initial partial scan |
| if (mInitialScanState == INITIAL_SCAN_STATE_AWAITING_RESPONSE) { |
| // Done with initial scan |
| setInitialScanState(INITIAL_SCAN_STATE_COMPLETE); |
| |
| if (wasConnectAttempted) { |
| Log.i(TAG, "Connection attempted with the reduced initial scans"); |
| schedulePeriodicScanTimer( |
| getScheduledSingleScanIntervalMs(mCurrentSingleScanScheduleIndex)); |
| mWifiMetrics.reportInitialPartialScan(mInitialPartialScanChannelCount, true); |
| mInitialPartialScanChannelCount = 0; |
| } else { |
| Log.i(TAG, "Connection was not attempted, issuing a full scan"); |
| startConnectivityScan(SCAN_IMMEDIATELY); |
| mFailedInitialPartialScan = true; |
| } |
| } else if (mInitialScanState == INITIAL_SCAN_STATE_COMPLETE) { |
| if (mFailedInitialPartialScan && wasConnectAttempted) { |
| // Initial scan failed, but following full scan succeeded |
| mWifiMetrics.reportInitialPartialScan(mInitialPartialScanChannelCount, false); |
| } |
| mFailedInitialPartialScan = false; |
| mInitialPartialScanChannelCount = 0; |
| } |
| } |
| |
| @Override |
| public void onFullResult(ScanResult fullScanResult) { |
| if (!mWifiEnabled || !mAutoJoinEnabled) { |
| return; |
| } |
| |
| if (mDbg) { |
| localLog("AllSingleScanListener onFullResult: " + fullScanResult.SSID |
| + " capabilities " + fullScanResult.capabilities); |
| } |
| |
| // When the scan result has radio chain info, ensure we throw away scan results |
| // not received with both radio chains (if |mUseSingleRadioChainScanResults| is false). |
| if (!mContext.getResources().getBoolean( |
| R.bool.config_wifi_framework_use_single_radio_chain_scan_results_network_selection) |
| && fullScanResult.radioChainInfos != null |
| && fullScanResult.radioChainInfos.length == 1) { |
| // Keep track of the number of dropped scan results for logging. |
| mNumScanResultsIgnoredDueToSingleRadioChain++; |
| return; |
| } |
| |
| mScanDetails.add(ScanResultUtil.toScanDetail(fullScanResult)); |
| } |
| } |
| |
| private final AllSingleScanListener mAllSingleScanListener = new AllSingleScanListener(); |
| |
| // Single scan results listener. A single scan is initiated when |
| // DisconnectedPNO scan found a valid network and woke up |
| // the system, or by the watchdog timer, or to form the timer based |
| // periodic scan. |
| // |
| // Note: This is the listener for the single scans initiated by the |
| // WifiConnectivityManager. |
| private class SingleScanListener implements WifiScanner.ScanListener { |
| private final boolean mIsFullBandScan; |
| |
| SingleScanListener(boolean isFullBandScan) { |
| mIsFullBandScan = isFullBandScan; |
| } |
| |
| @Override |
| public void onSuccess() { |
| } |
| |
| @Override |
| public void onFailure(int reason, String description) { |
| localLog("SingleScanListener onFailure:" |
| + " reason: " + reason + " description: " + description); |
| |
| // reschedule the scan |
| if (mSingleScanRestartCount++ < MAX_SCAN_RESTART_ALLOWED) { |
| scheduleDelayedSingleScan(mIsFullBandScan); |
| } else { |
| mSingleScanRestartCount = 0; |
| localLog("Failed to successfully start single scan for " |
| + MAX_SCAN_RESTART_ALLOWED + " times"); |
| } |
| } |
| |
| @Override |
| public void onPeriodChanged(int periodInMs) { |
| localLog("SingleScanListener onPeriodChanged: " |
| + "actual scan period " + periodInMs + "ms"); |
| } |
| |
| @Override |
| public void onResults(WifiScanner.ScanData[] results) { |
| mSingleScanRestartCount = 0; |
| } |
| |
| @Override |
| public void onFullResult(ScanResult fullScanResult) { |
| } |
| } |
| |
| // PNO scan results listener for both disconnected and connected PNO scanning. |
| // A PNO scan is initiated when screen is off. |
| private class PnoScanListener implements WifiScanner.PnoScanListener { |
| private List<ScanDetail> mScanDetails = new ArrayList<ScanDetail>(); |
| private int mLowRssiNetworkRetryDelay = |
| LOW_RSSI_NETWORK_RETRY_START_DELAY_MS; |
| |
| public void clearScanDetails() { |
| mScanDetails.clear(); |
| } |
| |
| // Reset to the start value when either a non-PNO scan is started or |
| // WifiNetworkSelector selects a candidate from the PNO scan results. |
| public void resetLowRssiNetworkRetryDelay() { |
| mLowRssiNetworkRetryDelay = LOW_RSSI_NETWORK_RETRY_START_DELAY_MS; |
| } |
| |
| @VisibleForTesting |
| public int getLowRssiNetworkRetryDelay() { |
| return mLowRssiNetworkRetryDelay; |
| } |
| |
| @Override |
| public void onSuccess() { |
| } |
| |
| @Override |
| public void onFailure(int reason, String description) { |
| localLog("PnoScanListener onFailure:" |
| + " reason: " + reason + " description: " + description); |
| |
| // reschedule the scan |
| if (mScanRestartCount++ < MAX_SCAN_RESTART_ALLOWED) { |
| scheduleDelayedConnectivityScan(RESTART_SCAN_DELAY_MS); |
| } else { |
| mScanRestartCount = 0; |
| localLog("Failed to successfully start PNO scan for " |
| + MAX_SCAN_RESTART_ALLOWED + " times"); |
| } |
| } |
| |
| @Override |
| public void onPeriodChanged(int periodInMs) { |
| localLog("PnoScanListener onPeriodChanged: " |
| + "actual scan period " + periodInMs + "ms"); |
| } |
| |
| // Currently the PNO scan results doesn't include IE, |
| // which contains information required by WifiNetworkSelector. Ignore them |
| // for now. |
| @Override |
| public void onResults(WifiScanner.ScanData[] results) { |
| } |
| |
| @Override |
| public void onFullResult(ScanResult fullScanResult) { |
| } |
| |
| @Override |
| public void onPnoNetworkFound(ScanResult[] results) { |
| for (ScanResult result: results) { |
| if (result.informationElements == null) { |
| localLog("Skipping scan result with null information elements"); |
| continue; |
| } |
| mScanDetails.add(ScanResultUtil.toScanDetail(result)); |
| } |
| |
| boolean wasConnectAttempted; |
| wasConnectAttempted = handleScanResults(mScanDetails, PNO_SCAN_LISTENER, false); |
| clearScanDetails(); |
| mScanRestartCount = 0; |
| |
| if (!wasConnectAttempted) { |
| // The scan results were rejected by WifiNetworkSelector due to low RSSI values |
| if (mLowRssiNetworkRetryDelay > LOW_RSSI_NETWORK_RETRY_MAX_DELAY_MS) { |
| mLowRssiNetworkRetryDelay = LOW_RSSI_NETWORK_RETRY_MAX_DELAY_MS; |
| } |
| scheduleDelayedConnectivityScan(mLowRssiNetworkRetryDelay); |
| |
| // Set up the delay value for next retry. |
| mLowRssiNetworkRetryDelay *= 2; |
| } else { |
| resetLowRssiNetworkRetryDelay(); |
| } |
| } |
| } |
| |
| private final PnoScanListener mPnoScanListener = new PnoScanListener(); |
| |
| private class OnNetworkUpdateListener implements |
| WifiConfigManager.OnNetworkUpdateListener { |
| @Override |
| public void onNetworkAdded(WifiConfiguration config) { |
| triggerScanOnNetworkChanges(); |
| } |
| @Override |
| public void onNetworkEnabled(WifiConfiguration config) { |
| triggerScanOnNetworkChanges(); |
| } |
| @Override |
| public void onNetworkRemoved(WifiConfiguration config) { |
| triggerScanOnNetworkChanges(); |
| } |
| @Override |
| public void onNetworkUpdated(WifiConfiguration newConfig, WifiConfiguration oldConfig) { |
| triggerScanOnNetworkChanges(); |
| } |
| @Override |
| public void onNetworkTemporarilyDisabled(WifiConfiguration config, int disableReason) { } |
| |
| @Override |
| public void onNetworkPermanentlyDisabled(WifiConfiguration config, int disableReason) { |
| triggerScanOnNetworkChanges(); |
| } |
| } |
| |
| private class OnSuggestionUpdateListener implements |
| WifiNetworkSuggestionsManager.OnSuggestionUpdateListener { |
| @Override |
| public void onSuggestionsAddedOrUpdated(List<WifiNetworkSuggestion> suggestions) { |
| triggerScanOnNetworkChanges(); |
| } |
| |
| @Override |
| public void onSuggestionsRemoved(List<WifiNetworkSuggestion> suggestions) { |
| triggerScanOnNetworkChanges(); |
| } |
| } |
| |
| private class ModeChangeCallback implements ActiveModeWarden.ModeChangeCallback { |
| @Override |
| public void onActiveModeManagerAdded(@NonNull ActiveModeManager activeModeManager) { |
| if (!(activeModeManager instanceof ClientModeManager)) return; |
| if (mVerboseLoggingEnabled) { |
| Log.v(TAG, "ModeManager added " + activeModeManager.getInterfaceName()); |
| } |
| if (activeModeManager.getRole() instanceof ClientInternetConnectivityRole) { |
| mClientModeManagers.add((ClientModeManager) activeModeManager); |
| } |
| setWifiEnabled(!mClientModeManagers.isEmpty()); |
| } |
| |
| @Override |
| public void onActiveModeManagerRemoved(@NonNull ActiveModeManager activeModeManager) { |
| if (!(activeModeManager instanceof ClientModeManager)) return; |
| if (mVerboseLoggingEnabled) { |
| Log.v(TAG, "ModeManager removed " + activeModeManager.getInterfaceName()); |
| } |
| // No need to check for role when mode manager is removed. |
| mClientModeManagers.remove(activeModeManager); |
| setWifiEnabled(!mClientModeManagers.isEmpty()); |
| } |
| |
| @Override |
| public void onActiveModeManagerRoleChanged(@NonNull ActiveModeManager activeModeManager) { |
| if (!(activeModeManager instanceof ClientModeManager)) return; |
| if (mVerboseLoggingEnabled) { |
| Log.v(TAG, "ModeManager role changed " + activeModeManager.getInterfaceName()); |
| } |
| if (activeModeManager.getRole() instanceof ClientInternetConnectivityRole) { |
| mClientModeManagers.add((ClientModeManager) activeModeManager); |
| } else { |
| mClientModeManagers.remove(activeModeManager); |
| } |
| setWifiEnabled(!mClientModeManagers.isEmpty()); |
| } |
| } |
| |
| /** |
| * WifiConnectivityManager constructor |
| */ |
| WifiConnectivityManager( |
| Context context, |
| ScoringParams scoringParams, |
| WifiConfigManager configManager, |
| WifiNetworkSuggestionsManager wifiNetworkSuggestionsManager, |
| WifiInfo wifiInfo, |
| WifiNetworkSelector networkSelector, |
| WifiConnectivityHelper connectivityHelper, |
| WifiLastResortWatchdog wifiLastResortWatchdog, |
| OpenNetworkNotifier openNetworkNotifier, |
| WifiMetrics wifiMetrics, |
| Handler handler, |
| Clock clock, |
| LocalLog localLog, |
| WifiScoreCard scoreCard, |
| BssidBlocklistMonitor bssidBlocklistMonitor, |
| WifiChannelUtilization wifiChannelUtilization, |
| PasspointManager passpointManager, |
| DeviceConfigFacade deviceConfigFacade, |
| ActiveModeWarden activeModeWarden) { |
| mContext = context; |
| mScoringParams = scoringParams; |
| mConfigManager = configManager; |
| mWifiNetworkSuggestionsManager = wifiNetworkSuggestionsManager; |
| mWifiInfo = wifiInfo; |
| mNetworkSelector = networkSelector; |
| mConnectivityHelper = connectivityHelper; |
| mWifiLastResortWatchdog = wifiLastResortWatchdog; |
| mOpenNetworkNotifier = openNetworkNotifier; |
| mWifiMetrics = wifiMetrics; |
| mEventHandler = handler; |
| mClock = clock; |
| mLocalLog = localLog; |
| mWifiScoreCard = scoreCard; |
| mBssidBlocklistMonitor = bssidBlocklistMonitor; |
| mWifiChannelUtilization = wifiChannelUtilization; |
| mPasspointManager = passpointManager; |
| mDeviceConfigFacade = deviceConfigFacade; |
| mActiveModeWarden = activeModeWarden; |
| |
| mAlarmManager = context.getSystemService(AlarmManager.class); |
| mPowerManager = mContext.getSystemService(PowerManager.class); |
| |
| // Listen for screen state change events. |
| // TODO: We should probably add a shared broadcast receiver in the wifi stack which |
| // can used by various modules to listen to common system events. Creating multiple |
| // broadcast receivers in each class within the wifi stack is *somewhat* expensive. |
| IntentFilter filter = new IntentFilter(); |
| filter.addAction(Intent.ACTION_SCREEN_ON); |
| filter.addAction(Intent.ACTION_SCREEN_OFF); |
| mContext.registerReceiver( |
| new BroadcastReceiver() { |
| @Override |
| public void onReceive(Context context, Intent intent) { |
| String action = intent.getAction(); |
| if (action.equals(Intent.ACTION_SCREEN_ON)) { |
| handleScreenStateChanged(true); |
| } else if (action.equals(Intent.ACTION_SCREEN_OFF)) { |
| handleScreenStateChanged(false); |
| } |
| } |
| }, filter, null, mEventHandler); |
| handleScreenStateChanged(mPowerManager.isInteractive()); |
| |
| // Listen to WifiConfigManager network update events |
| mConfigManager.addOnNetworkUpdateListener(new OnNetworkUpdateListener()); |
| // Listen to WifiNetworkSuggestionsManager suggestion update events |
| mWifiNetworkSuggestionsManager.addOnSuggestionUpdateListener( |
| new OnSuggestionUpdateListener()); |
| mActiveModeWarden.registerModeChangeCallback(new ModeChangeCallback()); |
| } |
| |
| private ClientModeManager getClientModeManager() { |
| // TODO(b/159052883): Handle multiple primary client mode impl instances. |
| // Needs a better handle on these multiple primary end to end use cases. |
| return mClientModeManagers.valueAt(0); |
| } |
| |
| /** Initialize single scanning schedules, and validate them */ |
| private int[] initializeScanningSchedule(int state) { |
| int[] scheduleSec; |
| |
| if (state == WIFI_STATE_CONNECTED) { |
| scheduleSec = mContext.getResources().getIntArray( |
| R.array.config_wifiConnectedScanIntervalScheduleSec); |
| } else if (state == WIFI_STATE_DISCONNECTED) { |
| scheduleSec = mContext.getResources().getIntArray( |
| R.array.config_wifiDisconnectedScanIntervalScheduleSec); |
| } else { |
| scheduleSec = null; |
| } |
| |
| boolean invalidConfig = false; |
| if (scheduleSec == null || scheduleSec.length == 0) { |
| invalidConfig = true; |
| } else { |
| for (int val : scheduleSec) { |
| if (val <= 0) { |
| invalidConfig = true; |
| break; |
| } |
| } |
| } |
| if (!invalidConfig) { |
| return scheduleSec; |
| } |
| |
| Log.e(TAG, "Configuration for wifi scanning schedule is mis-configured," |
| + "using default schedule"); |
| return DEFAULT_SCANNING_SCHEDULE_SEC; |
| } |
| |
| /** |
| * This checks the connection attempt rate and recommends whether the connection attempt |
| * should be skipped or not. This attempts to rate limit the rate of connections to |
| * prevent us from flapping between networks and draining battery rapidly. |
| */ |
| private boolean shouldSkipConnectionAttempt(Long timeMillis) { |
| Iterator<Long> attemptIter = mConnectionAttemptTimeStamps.iterator(); |
| // First evict old entries from the queue. |
| while (attemptIter.hasNext()) { |
| Long connectionAttemptTimeMillis = attemptIter.next(); |
| if ((timeMillis - connectionAttemptTimeMillis) |
| > MAX_CONNECTION_ATTEMPTS_TIME_INTERVAL_MS) { |
| attemptIter.remove(); |
| } else { |
| // This list is sorted by timestamps, so we can skip any more checks |
| break; |
| } |
| } |
| // If we've reached the max connection attempt rate, skip this connection attempt |
| return (mConnectionAttemptTimeStamps.size() >= MAX_CONNECTION_ATTEMPTS_RATE); |
| } |
| |
| /** |
| * Add the current connection attempt timestamp to our queue of connection attempts. |
| */ |
| private void noteConnectionAttempt(Long timeMillis) { |
| mConnectionAttemptTimeStamps.addLast(timeMillis); |
| } |
| |
| /** |
| * This is used to clear the connection attempt rate limiter. This is done when the user |
| * explicitly tries to connect to a specified network. |
| */ |
| private void clearConnectionAttemptTimeStamps() { |
| mConnectionAttemptTimeStamps.clear(); |
| } |
| |
| /** |
| * Attempt to connect to a network candidate. |
| * |
| * Based on the currently connected network, this menthod determines whether we should |
| * connect or roam to the network candidate recommended by WifiNetworkSelector. |
| */ |
| private void connectToNetwork(WifiConfiguration candidate) { |
| ScanResult scanResultCandidate = candidate.getNetworkSelectionStatus().getCandidate(); |
| if (scanResultCandidate == null) { |
| localLog("connectToNetwork: bad candidate - " + candidate |
| + " scanResult: " + scanResultCandidate); |
| return; |
| } |
| |
| String targetBssid = scanResultCandidate.BSSID; |
| String targetAssociationId = candidate.SSID + " : " + targetBssid; |
| |
| // Check if we are already connected or in the process of connecting to the target |
| // BSSID. mWifiInfo.mBSSID tracks the currently connected BSSID. This is checked just |
| // in case the firmware automatically roamed to a BSSID different from what |
| // WifiNetworkSelector selected. |
| if (targetBssid != null |
| && (targetBssid.equals(mLastConnectionAttemptBssid) |
| || targetBssid.equals(mWifiInfo.getBSSID())) |
| && SupplicantState.isConnecting(mWifiInfo.getSupplicantState())) { |
| localLog("connectToNetwork: Either already connected " |
| + "or is connecting to " + targetAssociationId); |
| return; |
| } |
| |
| if (candidate.BSSID != null |
| && !candidate.BSSID.equals(ClientModeImpl.SUPPLICANT_BSSID_ANY) |
| && !candidate.BSSID.equals(targetBssid)) { |
| localLog("connecToNetwork: target BSSID " + targetBssid + " does not match the " |
| + "config specified BSSID " + candidate.BSSID + ". Drop it!"); |
| return; |
| } |
| |
| long elapsedTimeMillis = mClock.getElapsedSinceBootMillis(); |
| if (!mScreenOn && shouldSkipConnectionAttempt(elapsedTimeMillis)) { |
| localLog("connectToNetwork: Too many connection attempts. Skipping this attempt!"); |
| mTotalConnectivityAttemptsRateLimited++; |
| return; |
| } |
| noteConnectionAttempt(elapsedTimeMillis); |
| |
| mLastConnectionAttemptBssid = targetBssid; |
| |
| WifiConfiguration currentConnectedNetwork = mConfigManager |
| .getConfiguredNetwork(mWifiInfo.getNetworkId()); |
| String currentAssociationId = (currentConnectedNetwork == null) ? "Disconnected" : |
| (mWifiInfo.getSSID() + " : " + mWifiInfo.getBSSID()); |
| |
| ClientModeManager clientModeManager = getClientModeManager(); |
| if (currentConnectedNetwork != null |
| && (currentConnectedNetwork.networkId == candidate.networkId |
| //TODO(b/36788683): re-enable linked configuration check |
| /* || currentConnectedNetwork.isLinked(candidate) */)) { |
| // Framework initiates roaming only if firmware doesn't support |
| // {@link android.net.wifi.WifiManager#WIFI_FEATURE_CONTROL_ROAMING}. |
| if (mConnectivityHelper.isFirmwareRoamingSupported()) { |
| // Keep this logging here for now to validate the firmware roaming behavior. |
| localLog("connectToNetwork: Roaming candidate - " + targetAssociationId + "." |
| + " The actual roaming target is up to the firmware."); |
| } else { |
| localLog("connectToNetwork: Roaming to " + targetAssociationId + " from " |
| + currentAssociationId); |
| clientModeManager.startRoamToNetwork(candidate.networkId, scanResultCandidate); |
| } |
| } else { |
| // Framework specifies the connection target BSSID if firmware doesn't support |
| // {@link android.net.wifi.WifiManager#WIFI_FEATURE_CONTROL_ROAMING} or the |
| // candidate configuration contains a specified BSSID. |
| if (mConnectivityHelper.isFirmwareRoamingSupported() && (candidate.BSSID == null |
| || candidate.BSSID.equals(ClientModeImpl.SUPPLICANT_BSSID_ANY))) { |
| targetBssid = ClientModeImpl.SUPPLICANT_BSSID_ANY; |
| localLog("connectToNetwork: Connect to " + candidate.SSID + ":" + targetBssid |
| + " from " + currentAssociationId); |
| } else { |
| localLog("connectToNetwork: Connect to " + targetAssociationId + " from " |
| + currentAssociationId); |
| } |
| clientModeManager.startConnectToNetwork( |
| candidate.networkId, Process.WIFI_UID, targetBssid); |
| } |
| } |
| |
| // Helper for selecting the band for connectivity scan |
| private int getScanBand() { |
| return getScanBand(true); |
| } |
| |
| private int getScanBand(boolean isFullBandScan) { |
| if (isFullBandScan) { |
| return WifiScanner.WIFI_BAND_ALL; |
| } else { |
| // Use channel list instead. |
| return WifiScanner.WIFI_BAND_UNSPECIFIED; |
| } |
| } |
| |
| // Helper for setting the channels for connectivity scan when band is unspecified. Returns |
| // false if we can't retrieve the info. |
| // If connected, return channels used for the connected network |
| // If disconnected, return channels used for any network. |
| private boolean setScanChannels(ScanSettings settings) { |
| Set<Integer> freqs; |
| |
| WifiConfiguration config = getClientModeManager().getCurrentWifiConfiguration(); |
| if (config == null) { |
| long ageInMillis = 1000 * 60 * mContext.getResources().getInteger( |
| R.integer.config_wifiInitialPartialScanChannelCacheAgeMins); |
| int maxCount = mContext.getResources().getInteger( |
| R.integer.config_wifiInitialPartialScanChannelMaxCount); |
| freqs = fetchChannelSetForPartialScan(maxCount, ageInMillis); |
| } else { |
| freqs = fetchChannelSetForNetworkForPartialScan(config.networkId); |
| } |
| |
| if (freqs != null && freqs.size() != 0) { |
| int index = 0; |
| settings.channels = new WifiScanner.ChannelSpec[freqs.size()]; |
| for (Integer freq : freqs) { |
| settings.channels[index++] = new WifiScanner.ChannelSpec(freq); |
| } |
| return true; |
| } else { |
| localLog("No history scan channels found, Perform full band scan"); |
| return false; |
| } |
| } |
| |
| /** |
| * Add the channels into the channel set with a size limit. |
| * If maxCount equals to 0, will add all available channels into the set. |
| * @param channelSet Target set for adding channel to. |
| * @param config Network for query channel from WifiScoreCard |
| * @param maxCount Size limit of the set. If equals to 0, means no limit. |
| * @param ageInMillis Only consider channel info whose timestamps are younger than this value. |
| * @return True if all available channels for this network are added, otherwise false. |
| */ |
| private boolean addChannelFromWifiScoreCard(@NonNull Set<Integer> channelSet, |
| @NonNull WifiConfiguration config, int maxCount, long ageInMillis) { |
| WifiScoreCard.PerNetwork network = mWifiScoreCard.lookupNetwork(config.SSID); |
| for (Integer channel : network.getFrequencies(ageInMillis)) { |
| if (maxCount > 0 && channelSet.size() >= maxCount) { |
| localLog("addChannelFromWifiScoreCard: size limit reached for network:" |
| + config.SSID); |
| return false; |
| } |
| channelSet.add(channel); |
| } |
| return true; |
| } |
| |
| /** |
| * Fetch channel set for target network. |
| */ |
| @VisibleForTesting |
| public Set<Integer> fetchChannelSetForNetworkForPartialScan(int networkId) { |
| WifiConfiguration config = mConfigManager.getConfiguredNetwork(networkId); |
| if (config == null) { |
| return null; |
| } |
| final int maxNumActiveChannelsForPartialScans = mContext.getResources().getInteger( |
| R.integer.config_wifi_framework_associated_partial_scan_max_num_active_channels); |
| Set<Integer> channelSet = new HashSet<>(); |
| // First add the currently connected network channel. |
| if (mWifiInfo.getFrequency() > 0) { |
| channelSet.add(mWifiInfo.getFrequency()); |
| } |
| // Then get channels for the network. |
| addChannelFromWifiScoreCard(channelSet, config, maxNumActiveChannelsForPartialScans, |
| CHANNEL_LIST_AGE_MS); |
| return channelSet; |
| } |
| |
| /** |
| * Fetch channel set for all saved and suggestion non-passpoint network for partial scan. |
| */ |
| @VisibleForTesting |
| public Set<Integer> fetchChannelSetForPartialScan(int maxCount, long ageInMillis) { |
| List<WifiConfiguration> networks = getAllScanOptimizationNetworks(); |
| if (networks.isEmpty()) { |
| return null; |
| } |
| |
| // Sort the networks with the most frequent ones at the front of the network list. |
| Collections.sort(networks, mConfigManager.getScanListComparator()); |
| |
| Set<Integer> channelSet = new HashSet<>(); |
| |
| for (WifiConfiguration config : networks) { |
| if (!addChannelFromWifiScoreCard(channelSet, config, maxCount, ageInMillis)) { |
| return channelSet; |
| } |
| } |
| |
| return channelSet; |
| } |
| |
| // Watchdog timer handler |
| private void watchdogHandler() { |
| // Schedule the next timer and start a single scan if we are in disconnected state. |
| // Otherwise, the watchdog timer will be scheduled when entering disconnected |
| // state. |
| if (mWifiState == WIFI_STATE_DISCONNECTED) { |
| localLog("start a single scan from watchdogHandler"); |
| |
| scheduleWatchdogTimer(); |
| startSingleScan(true, WIFI_WORK_SOURCE); |
| } |
| } |
| |
| private void triggerScanOnNetworkChanges() { |
| if (mScreenOn) { |
| // Update scanning schedule if needed |
| if (updateSingleScanningSchedule()) { |
| localLog("Saved networks / suggestions updated impacting single scan schedule"); |
| startConnectivityScan(false); |
| } |
| } else { |
| // Update the PNO scan network list when screen is off. Here we |
| // rely on startConnectivityScan() to perform all the checks and clean up. |
| localLog("Saved networks / suggestions updated impacting pno scan"); |
| startConnectivityScan(false); |
| } |
| } |
| |
| // Start a single scan and set up the interval for next single scan. |
| private void startPeriodicSingleScan() { |
| // Reaching here with scanning schedule is null means this is a false timer alarm |
| if (getSingleScanningSchedule() == null) { |
| return; |
| } |
| |
| long currentTimeStamp = mClock.getElapsedSinceBootMillis(); |
| |
| if (mLastPeriodicSingleScanTimeStamp != RESET_TIME_STAMP) { |
| long msSinceLastScan = currentTimeStamp - mLastPeriodicSingleScanTimeStamp; |
| if (msSinceLastScan < getScheduledSingleScanIntervalMs(0)) { |
| localLog("Last periodic single scan started " + msSinceLastScan |
| + "ms ago, defer this new scan request."); |
| schedulePeriodicScanTimer( |
| getScheduledSingleScanIntervalMs(0) - (int) msSinceLastScan); |
| return; |
| } |
| } |
| |
| boolean isScanNeeded = true; |
| boolean isFullBandScan = true; |
| |
| boolean isShortTimeSinceLastNetworkSelection = |
| ((currentTimeStamp - mLastNetworkSelectionTimeStamp) |
| <= 1000 * mContext.getResources().getInteger( |
| R.integer.config_wifiConnectedHighRssiScanMinimumWindowSizeSec)); |
| |
| boolean isGoodLinkAndAcceptableInternetAndShortTimeSinceLastNetworkSelection = |
| mNetworkSelector.hasSufficientLinkQuality(mWifiInfo) |
| && mNetworkSelector.hasInternetOrExpectNoInternet(mWifiInfo) |
| && isShortTimeSinceLastNetworkSelection; |
| // Check it is one of following conditions to skip scan (with firmware roaming) |
| // or do partial scan only (without firmware roaming). |
| // 1) Network is sufficient |
| // 2) link is good, internet status is acceptable |
| // and it is a short time since last network selection |
| // 3) There is active stream such that scan will be likely disruptive |
| if (mWifiState == WIFI_STATE_CONNECTED |
| && (mNetworkSelector.isNetworkSufficient(mWifiInfo) |
| || isGoodLinkAndAcceptableInternetAndShortTimeSinceLastNetworkSelection |
| || mNetworkSelector.hasActiveStream(mWifiInfo))) { |
| // If only partial scan is proposed and firmware roaming control is supported, |
| // we will not issue any scan because firmware roaming will take care of |
| // intra-SSID roam. |
| if (mConnectivityHelper.isFirmwareRoamingSupported()) { |
| localLog("No partial scan because firmware roaming is supported."); |
| isScanNeeded = false; |
| } else { |
| localLog("No full band scan because current network is sufficient"); |
| isFullBandScan = false; |
| } |
| } |
| |
| if (isScanNeeded) { |
| mLastPeriodicSingleScanTimeStamp = currentTimeStamp; |
| |
| if (mWifiState == WIFI_STATE_DISCONNECTED |
| && mInitialScanState == INITIAL_SCAN_STATE_START) { |
| startSingleScan(false, WIFI_WORK_SOURCE); |
| |
| // Note, initial partial scan may fail due to lack of channel history |
| // Hence, we verify state before changing to AWIATING_RESPONSE |
| if (mInitialScanState == INITIAL_SCAN_STATE_START) { |
| setInitialScanState(INITIAL_SCAN_STATE_AWAITING_RESPONSE); |
| mWifiMetrics.incrementInitialPartialScanCount(); |
| } |
| // No scheduling for another scan (until we get the results) |
| return; |
| } |
| |
| startSingleScan(isFullBandScan, WIFI_WORK_SOURCE); |
| schedulePeriodicScanTimer( |
| getScheduledSingleScanIntervalMs(mCurrentSingleScanScheduleIndex)); |
| |
| // Set up the next scan interval in an exponential backoff fashion. |
| mCurrentSingleScanScheduleIndex++; |
| } else { |
| // Since we already skipped this scan, keep the same scan interval for next scan. |
| schedulePeriodicScanTimer( |
| getScheduledSingleScanIntervalMs(mCurrentSingleScanScheduleIndex)); |
| } |
| } |
| |
| // Retrieve a value from single scanning schedule in ms |
| private int getScheduledSingleScanIntervalMs(int index) { |
| synchronized (mLock) { |
| if (mCurrentSingleScanScheduleSec == null) { |
| Log.e(TAG, "Invalid attempt to get schedule interval, Schedule array is null "); |
| |
| // Use a default value |
| return DEFAULT_SCANNING_SCHEDULE_SEC[0] * 1000; |
| } |
| |
| if (index >= mCurrentSingleScanScheduleSec.length) { |
| index = mCurrentSingleScanScheduleSec.length - 1; |
| } |
| return getScanIntervalWithPowerSaveMultiplier( |
| mCurrentSingleScanScheduleSec[index] * 1000); |
| } |
| } |
| |
| private int getScanIntervalWithPowerSaveMultiplier(int interval) { |
| if (!mDeviceConfigFacade.isWifiBatterySaverEnabled()) { |
| return interval; |
| } |
| return mPowerManager.isPowerSaveMode() |
| ? POWER_SAVE_SCAN_INTERVAL_MULTIPLIER * interval : interval; |
| } |
| |
| // Set the single scanning schedule |
| private void setSingleScanningSchedule(int[] scheduleSec) { |
| synchronized (mLock) { |
| mCurrentSingleScanScheduleSec = scheduleSec; |
| } |
| } |
| |
| // Get the single scanning schedule |
| private int[] getSingleScanningSchedule() { |
| synchronized (mLock) { |
| return mCurrentSingleScanScheduleSec; |
| } |
| } |
| |
| // Update the single scanning schedule if needed, and return true if update occurs |
| private boolean updateSingleScanningSchedule() { |
| if (!mWifiEnabled || !mAutoJoinEnabled) { |
| return false; |
| } |
| if (mWifiState != WIFI_STATE_CONNECTED) { |
| // No need to update the scanning schedule |
| return false; |
| } |
| |
| boolean shouldUseSingleSavedNetworkSchedule = useSingleSavedNetworkSchedule(); |
| |
| if (mCurrentSingleScanScheduleSec == mConnectedSingleScanScheduleSec |
| && shouldUseSingleSavedNetworkSchedule) { |
| mCurrentSingleScanScheduleSec = mConnectedSingleSavedNetworkSingleScanScheduleSec; |
| return true; |
| } |
| if (mCurrentSingleScanScheduleSec == mConnectedSingleSavedNetworkSingleScanScheduleSec |
| && !shouldUseSingleSavedNetworkSchedule) { |
| mCurrentSingleScanScheduleSec = mConnectedSingleScanScheduleSec; |
| return true; |
| } |
| return false; |
| } |
| |
| // Set initial scan state |
| private void setInitialScanState(int state) { |
| Log.i(TAG, "SetInitialScanState to : " + state); |
| mInitialScanState = state; |
| } |
| |
| // Reset the last periodic single scan time stamp so that the next periodic single |
| // scan can start immediately. |
| private void resetLastPeriodicSingleScanTimeStamp() { |
| mLastPeriodicSingleScanTimeStamp = RESET_TIME_STAMP; |
| } |
| |
| // Periodic scan timer handler |
| private void periodicScanTimerHandler() { |
| localLog("periodicScanTimerHandler"); |
| |
| // Schedule the next timer and start a single scan if screen is on. |
| if (mScreenOn) { |
| startPeriodicSingleScan(); |
| } |
| } |
| |
| // Start a single scan |
| private void startForcedSingleScan(boolean isFullBandScan, WorkSource workSource) { |
| mPnoScanListener.resetLowRssiNetworkRetryDelay(); |
| |
| ScanSettings settings = new ScanSettings(); |
| if (!isFullBandScan) { |
| if (!setScanChannels(settings)) { |
| isFullBandScan = true; |
| // Skip the initial scan since no channel history available |
| setInitialScanState(INITIAL_SCAN_STATE_COMPLETE); |
| } else { |
| mInitialPartialScanChannelCount = settings.channels.length; |
| } |
| } |
| settings.type = WifiScanner.SCAN_TYPE_HIGH_ACCURACY; // always do high accuracy scans. |
| settings.band = getScanBand(isFullBandScan); |
| settings.reportEvents = WifiScanner.REPORT_EVENT_FULL_SCAN_RESULT |
| | WifiScanner.REPORT_EVENT_AFTER_EACH_SCAN; |
| settings.numBssidsPerScan = 0; |
| settings.hiddenNetworks.clear(); |
| // retrieve the list of hidden network SSIDs from saved network to scan for |
| settings.hiddenNetworks.addAll(mConfigManager.retrieveHiddenNetworkList()); |
| // retrieve the list of hidden network SSIDs from Network suggestion to scan for |
| settings.hiddenNetworks.addAll(mWifiNetworkSuggestionsManager.retrieveHiddenNetworkList()); |
| |
| SingleScanListener singleScanListener = |
| new SingleScanListener(isFullBandScan); |
| mScanner.startScan( |
| settings, new HandlerExecutor(mEventHandler), singleScanListener, workSource); |
| mWifiMetrics.incrementConnectivityOneshotScanCount(); |
| } |
| |
| private void startSingleScan(boolean isFullBandScan, WorkSource workSource) { |
| if (!mWifiEnabled || !mAutoJoinEnabled) { |
| return; |
| } |
| startForcedSingleScan(isFullBandScan, workSource); |
| } |
| |
| // Start a periodic scan when screen is on |
| private void startPeriodicScan(boolean scanImmediately) { |
| mPnoScanListener.resetLowRssiNetworkRetryDelay(); |
| |
| // No connectivity scan if auto roaming is disabled. |
| if (mWifiState == WIFI_STATE_CONNECTED && !mContext.getResources().getBoolean( |
| R.bool.config_wifi_framework_enable_associated_network_selection)) { |
| return; |
| } |
| |
| // Due to b/28020168, timer based single scan will be scheduled |
| // to provide periodic scan in an exponential backoff fashion. |
| if (scanImmediately) { |
| resetLastPeriodicSingleScanTimeStamp(); |
| } |
| mCurrentSingleScanScheduleIndex = 0; |
| startPeriodicSingleScan(); |
| } |
| |
| private int deviceMobilityStateToPnoScanIntervalMs(@DeviceMobilityState int state) { |
| switch (state) { |
| case WifiManager.DEVICE_MOBILITY_STATE_UNKNOWN: |
| case WifiManager.DEVICE_MOBILITY_STATE_LOW_MVMT: |
| case WifiManager.DEVICE_MOBILITY_STATE_HIGH_MVMT: |
| return getScanIntervalWithPowerSaveMultiplier(mContext.getResources() |
| .getInteger(R.integer.config_wifiMovingPnoScanIntervalMillis)); |
| case WifiManager.DEVICE_MOBILITY_STATE_STATIONARY: |
| return getScanIntervalWithPowerSaveMultiplier(mContext.getResources() |
| .getInteger(R.integer.config_wifiStationaryPnoScanIntervalMillis)); |
| default: |
| return -1; |
| } |
| } |
| |
| /** |
| * Pass device mobility state to WifiChannelUtilization and |
| * alter the PNO scan interval based on the current device mobility state. |
| * If the device is stationary, it will likely not find many new Wifi networks. Thus, increase |
| * the interval between scans. Decrease the interval between scans if the device begins to move |
| * again. |
| * @param newState the new device mobility state |
| */ |
| public void setDeviceMobilityState(@DeviceMobilityState int newState) { |
| int oldDeviceMobilityState = mDeviceMobilityState; |
| localLog("Device mobility state changed. state=" + newState); |
| int newPnoScanIntervalMs = deviceMobilityStateToPnoScanIntervalMs(newState); |
| if (newPnoScanIntervalMs < 0) { |
| Log.e(TAG, "Invalid device mobility state: " + newState); |
| return; |
| } |
| mDeviceMobilityState = newState; |
| mWifiChannelUtilization.setDeviceMobilityState(newState); |
| |
| int oldPnoScanIntervalMs = deviceMobilityStateToPnoScanIntervalMs(oldDeviceMobilityState); |
| if (newPnoScanIntervalMs == oldPnoScanIntervalMs) { |
| if (mPnoScanStarted) { |
| mWifiMetrics.logPnoScanStop(); |
| mWifiMetrics.enterDeviceMobilityState(newState); |
| mWifiMetrics.logPnoScanStart(); |
| } else { |
| mWifiMetrics.enterDeviceMobilityState(newState); |
| } |
| } else { |
| Log.d(TAG, "PNO Scan Interval changed to " + newPnoScanIntervalMs + " ms."); |
| |
| if (mPnoScanStarted) { |
| Log.d(TAG, "Restarting PNO Scan with new scan interval"); |
| stopPnoScan(); |
| mWifiMetrics.enterDeviceMobilityState(newState); |
| startDisconnectedPnoScan(); |
| } else { |
| mWifiMetrics.enterDeviceMobilityState(newState); |
| } |
| } |
| } |
| |
| // Start a DisconnectedPNO scan when screen is off and Wifi is disconnected |
| private void startDisconnectedPnoScan() { |
| // Initialize PNO settings |
| PnoSettings pnoSettings = new PnoSettings(); |
| List<PnoSettings.PnoNetwork> pnoNetworkList = retrievePnoNetworkList(); |
| int listSize = pnoNetworkList.size(); |
| |
| if (listSize == 0) { |
| // No saved network |
| localLog("No saved network for starting disconnected PNO."); |
| return; |
| } |
| |
| pnoSettings.networkList = new PnoSettings.PnoNetwork[listSize]; |
| pnoSettings.networkList = pnoNetworkList.toArray(pnoSettings.networkList); |
| pnoSettings.min6GHzRssi = mScoringParams.getEntryRssi(ScanResult.BAND_6_GHZ_START_FREQ_MHZ); |
| pnoSettings.min5GHzRssi = mScoringParams.getEntryRssi(ScanResult.BAND_5_GHZ_START_FREQ_MHZ); |
| pnoSettings.min24GHzRssi = mScoringParams.getEntryRssi( |
| ScanResult.BAND_24_GHZ_START_FREQ_MHZ); |
| |
| // Initialize scan settings |
| ScanSettings scanSettings = new ScanSettings(); |
| scanSettings.band = getScanBand(); |
| scanSettings.reportEvents = WifiScanner.REPORT_EVENT_NO_BATCH; |
| scanSettings.numBssidsPerScan = 0; |
| scanSettings.periodInMs = deviceMobilityStateToPnoScanIntervalMs(mDeviceMobilityState); |
| |
| mPnoScanListener.clearScanDetails(); |
| |
| mScanner.startDisconnectedPnoScan( |
| scanSettings, pnoSettings, new HandlerExecutor(mEventHandler), mPnoScanListener); |
| mPnoScanStarted = true; |
| mWifiMetrics.logPnoScanStart(); |
| } |
| |
| private @NonNull List<WifiConfiguration> getAllScanOptimizationNetworks() { |
| List<WifiConfiguration> networks = mConfigManager.getSavedNetworks(-1); |
| networks.addAll(mWifiNetworkSuggestionsManager.getAllScanOptimizationSuggestionNetworks()); |
| // remove all auto-join disabled or network selection disabled network. |
| networks.removeIf(config -> !config.allowAutojoin |
| || !config.getNetworkSelectionStatus().isNetworkEnabled()); |
| return networks; |
| } |
| |
| /** |
| * Retrieve the PnoNetworks from Saved and suggestion non-passpoint network. |
| */ |
| @VisibleForTesting |
| public List<PnoSettings.PnoNetwork> retrievePnoNetworkList() { |
| List<WifiConfiguration> networks = getAllScanOptimizationNetworks(); |
| |
| if (networks.isEmpty()) { |
| return Collections.EMPTY_LIST; |
| } |
| Collections.sort(networks, mConfigManager.getScanListComparator()); |
| boolean pnoFrequencyCullingEnabled = mContext.getResources() |
| .getBoolean(R.bool.config_wifiPnoFrequencyCullingEnabled); |
| |
| List<PnoSettings.PnoNetwork> pnoList = new ArrayList<>(); |
| Set<WifiScanner.PnoSettings.PnoNetwork> pnoSet = new HashSet<>(); |
| for (WifiConfiguration config : networks) { |
| WifiScanner.PnoSettings.PnoNetwork pnoNetwork = |
| WifiConfigurationUtil.createPnoNetwork(config); |
| if (pnoSet.contains(pnoNetwork)) { |
| continue; |
| } |
| pnoList.add(pnoNetwork); |
| pnoSet.add(pnoNetwork); |
| if (!pnoFrequencyCullingEnabled) { |
| continue; |
| } |
| Set<Integer> channelList = new HashSet<>(); |
| addChannelFromWifiScoreCard(channelList, config, 0, |
| MAX_PNO_SCAN_FREQUENCY_AGE_MS); |
| pnoNetwork.frequencies = channelList.stream().mapToInt(Integer::intValue).toArray(); |
| localLog("retrievePnoNetworkList " + pnoNetwork.ssid + ":" |
| + Arrays.toString(pnoNetwork.frequencies)); |
| } |
| return pnoList; |
| } |
| |
| // Stop PNO scan. |
| private void stopPnoScan() { |
| if (!mPnoScanStarted) return; |
| |
| mScanner.stopPnoScan(mPnoScanListener); |
| mPnoScanStarted = false; |
| mWifiMetrics.logPnoScanStop(); |
| } |
| |
| // Set up watchdog timer |
| private void scheduleWatchdogTimer() { |
| localLog("scheduleWatchdogTimer"); |
| |
| mAlarmManager.set(AlarmManager.ELAPSED_REALTIME_WAKEUP, |
| mClock.getElapsedSinceBootMillis() + WATCHDOG_INTERVAL_MS, |
| WATCHDOG_TIMER_TAG, |
| mWatchdogListener, mEventHandler); |
| } |
| |
| // Schedules a delayed partial scan, which will scan the frequencies in mCachedWifiCandidates. |
| private void scheduleDelayedPartialScan(long delayMillis) { |
| mAlarmManager.set(AlarmManager.ELAPSED_REALTIME_WAKEUP, |
| mClock.getElapsedSinceBootMillis() + delayMillis, DELAYED_PARTIAL_SCAN_TIMER_TAG, |
| mDelayedPartialScanTimerListener, mEventHandler); |
| mDelayedPartialScanTimerSet = true; |
| } |
| |
| // Cancel the delayed partial scan timer. |
| private void cancelDelayedPartialScan() { |
| if (mDelayedPartialScanTimerSet) { |
| mAlarmManager.cancel(mDelayedPartialScanTimerListener); |
| mDelayedPartialScanTimerSet = false; |
| } |
| } |
| |
| // Set up periodic scan timer |
| private void schedulePeriodicScanTimer(int intervalMs) { |
| mAlarmManager.set(AlarmManager.ELAPSED_REALTIME_WAKEUP, |
| mClock.getElapsedSinceBootMillis() + intervalMs, |
| PERIODIC_SCAN_TIMER_TAG, |
| mPeriodicScanTimerListener, mEventHandler); |
| mPeriodicScanTimerSet = true; |
| } |
| |
| // Cancel periodic scan timer |
| private void cancelPeriodicScanTimer() { |
| if (mPeriodicScanTimerSet) { |
| mAlarmManager.cancel(mPeriodicScanTimerListener); |
| mPeriodicScanTimerSet = false; |
| } |
| } |
| |
| // Set up timer to start a delayed single scan after RESTART_SCAN_DELAY_MS |
| private void scheduleDelayedSingleScan(boolean isFullBandScan) { |
| localLog("scheduleDelayedSingleScan"); |
| |
| RestartSingleScanListener restartSingleScanListener = |
| new RestartSingleScanListener(isFullBandScan); |
| mAlarmManager.set(AlarmManager.ELAPSED_REALTIME_WAKEUP, |
| mClock.getElapsedSinceBootMillis() + RESTART_SCAN_DELAY_MS, |
| RESTART_SINGLE_SCAN_TIMER_TAG, |
| restartSingleScanListener, mEventHandler); |
| } |
| |
| // Set up timer to start a delayed scan after msFromNow milli-seconds |
| private void scheduleDelayedConnectivityScan(int msFromNow) { |
| localLog("scheduleDelayedConnectivityScan"); |
| |
| mAlarmManager.set(AlarmManager.ELAPSED_REALTIME_WAKEUP, |
| mClock.getElapsedSinceBootMillis() + msFromNow, |
| RESTART_CONNECTIVITY_SCAN_TIMER_TAG, |
| mRestartScanListener, mEventHandler); |
| |
| } |
| |
| // Start a connectivity scan. The scan method is chosen according to |
| // the current screen state and WiFi state. |
| private void startConnectivityScan(boolean scanImmediately) { |
| localLog("startConnectivityScan: screenOn=" + mScreenOn |
| + " wifiState=" + stateToString(mWifiState) |
| + " scanImmediately=" + scanImmediately |
| + " wifiEnabled=" + mWifiEnabled |
| + " wifiConnectivityManagerEnabled=" |
| + mAutoJoinEnabled); |
| |
| if (!mWifiEnabled || !mAutoJoinEnabled) { |
| return; |
| } |
| |
| // Always stop outstanding connecivity scan if there is any |
| stopConnectivityScan(); |
| |
| // Don't start a connectivity scan while Wifi is in the transition |
| // between connected and disconnected states. |
| if ((mWifiState != WIFI_STATE_CONNECTED && mWifiState != WIFI_STATE_DISCONNECTED) |
| || (getSingleScanningSchedule() == null)) { |
| return; |
| } |
| |
| if (mScreenOn) { |
| startPeriodicScan(scanImmediately); |
| } else { |
| if (mWifiState == WIFI_STATE_DISCONNECTED && !mPnoScanStarted) { |
| startDisconnectedPnoScan(); |
| } |
| } |
| |
| } |
| |
| // Stop connectivity scan if there is any. |
| private void stopConnectivityScan() { |
| // Due to b/28020168, timer based single scan will be scheduled |
| // to provide periodic scan in an exponential backoff fashion. |
| cancelPeriodicScanTimer(); |
| cancelDelayedPartialScan(); |
| stopPnoScan(); |
| } |
| |
| /** |
| * Handler for screen state (on/off) changes |
| */ |
| private void handleScreenStateChanged(boolean screenOn) { |
| localLog("handleScreenStateChanged: screenOn=" + screenOn); |
| |
| mScreenOn = screenOn; |
| |
| if (mWifiState == WIFI_STATE_DISCONNECTED |
| && mContext.getResources().getBoolean(R.bool.config_wifiEnablePartialInitialScan)) { |
| setInitialScanState(INITIAL_SCAN_STATE_START); |
| } |
| |
| mOpenNetworkNotifier.handleScreenStateChanged(screenOn); |
| |
| startConnectivityScan(SCAN_ON_SCHEDULE); |
| } |
| |
| /** |
| * Helper function that converts the WIFI_STATE_XXX constants to string |
| */ |
| private static String stateToString(int state) { |
| switch (state) { |
| case WIFI_STATE_CONNECTED: |
| return "connected"; |
| case WIFI_STATE_DISCONNECTED: |
| return "disconnected"; |
| case WIFI_STATE_TRANSITIONING: |
| return "transitioning"; |
| default: |
| return "unknown"; |
| } |
| } |
| |
| /** |
| * Check if Single saved network schedule should be used |
| * This is true if the one of the following is satisfied: |
| * 1. Device has a total of 1 network whether saved, passpoint, or suggestion. |
| * 2. The device is connected to that network. |
| */ |
| private boolean useSingleSavedNetworkSchedule() { |
| WifiConfiguration currentNetwork = getClientModeManager().getCurrentWifiConfiguration(); |
| if (currentNetwork == null) { |
| localLog("Current network is missing, may caused by remove network and disconnecting "); |
| return false; |
| } |
| List<WifiConfiguration> savedNetworks = |
| mConfigManager.getSavedNetworks(Process.WIFI_UID); |
| // If we have multiple saved networks, then no need to proceed |
| if (savedNetworks.size() > 1) { |
| return false; |
| } |
| |
| List<PasspointConfiguration> passpointNetworks = |
| mPasspointManager.getProviderConfigs(Process.WIFI_UID, true); |
| // If we have multiple networks (saved + passpoint), then no need to proceed |
| if (passpointNetworks.size() + savedNetworks.size() > 1) { |
| return false; |
| } |
| |
| Set<WifiNetworkSuggestion> suggestionsNetworks = |
| mWifiNetworkSuggestionsManager.getAllApprovedNetworkSuggestions(); |
| // If total size not equal to 1, then no need to proceed |
| if (passpointNetworks.size() + savedNetworks.size() + suggestionsNetworks.size() != 1) { |
| return false; |
| } |
| |
| // Next verify that this network is the one device is connected to |
| int currentNetworkId = currentNetwork.networkId; |
| |
| // If we have a single saved network, and we are connected to it, return true. |
| if (savedNetworks.size() == 1) { |
| return (savedNetworks.get(0).networkId == currentNetworkId); |
| } |
| |
| // If we have a single passpoint network, and we are connected to it, return true. |
| if (passpointNetworks.size() == 1) { |
| String passpointKey = passpointNetworks.get(0).getUniqueId(); |
| WifiConfiguration config = mConfigManager.getConfiguredNetwork(passpointKey); |
| return (config != null && config.networkId == currentNetworkId); |
| } |
| |
| // If we have a single suggestion network, and we are connected to it, return true. |
| WifiNetworkSuggestion network = suggestionsNetworks.iterator().next(); |
| String suggestionKey = network.getWifiConfiguration().getKey(); |
| WifiConfiguration config = mConfigManager.getConfiguredNetwork(suggestionKey); |
| return (config != null && config.networkId == currentNetworkId); |
| } |
| |
| private int[] initSingleSavedNetworkSchedule() { |
| int[] schedule = mContext.getResources().getIntArray( |
| R.array.config_wifiSingleSavedNetworkConnectedScanIntervalScheduleSec); |
| if (schedule == null || schedule.length == 0) { |
| return null; |
| } |
| |
| for (int val : schedule) { |
| if (val <= 0) { |
| return null; |
| } |
| } |
| return schedule; |
| } |
| |
| /** |
| * Handler for WiFi state (connected/disconnected) changes |
| */ |
| public void handleConnectionStateChanged(ActiveModeManager activeModeManager, int state) { |
| if (!mClientModeManagers.contains(activeModeManager)) { |
| Log.w(TAG, "Ignoring call from non primary Mode Manager" |
| + activeModeManager.getRole(), new Throwable()); |
| return; |
| } |
| localLog("handleConnectionStateChanged: state=" + stateToString(state)); |
| |
| if (mConnectedSingleScanScheduleSec == null) { |
| mConnectedSingleScanScheduleSec = initializeScanningSchedule(WIFI_STATE_CONNECTED); |
| } |
| if (mDisconnectedSingleScanScheduleSec == null) { |
| mDisconnectedSingleScanScheduleSec = |
| initializeScanningSchedule(WIFI_STATE_DISCONNECTED); |
| } |
| if (mConnectedSingleSavedNetworkSingleScanScheduleSec == null) { |
| mConnectedSingleSavedNetworkSingleScanScheduleSec = |
| initSingleSavedNetworkSchedule(); |
| if (mConnectedSingleSavedNetworkSingleScanScheduleSec == null) { |
| mConnectedSingleSavedNetworkSingleScanScheduleSec = mConnectedSingleScanScheduleSec; |
| } |
| } |
| |
| mWifiState = state; |
| |
| // Reset BSSID of last connection attempt and kick off |
| // the watchdog timer if entering disconnected state. |
| if (mWifiState == WIFI_STATE_DISCONNECTED) { |
| mLastConnectionAttemptBssid = null; |
| scheduleWatchdogTimer(); |
| // Switch to the disconnected scanning schedule |
| setSingleScanningSchedule(mDisconnectedSingleScanScheduleSec); |
| startConnectivityScan(SCAN_IMMEDIATELY); |
| } else if (mWifiState == WIFI_STATE_CONNECTED) { |
| if (useSingleSavedNetworkSchedule()) { |
| // Switch to Single-Saved-Network connected schedule |
| setSingleScanningSchedule(mConnectedSingleSavedNetworkSingleScanScheduleSec); |
| } else { |
| // Switch to connected single scanning schedule |
| setSingleScanningSchedule(mConnectedSingleScanScheduleSec); |
| } |
| startConnectivityScan(SCAN_ON_SCHEDULE); |
| } else { |
| // Intermediate state, no applicable single scanning schedule |
| setSingleScanningSchedule(null); |
| startConnectivityScan(SCAN_ON_SCHEDULE); |
| } |
| } |
| |
| /** |
| * Handler when a WiFi connection attempt ended. |
| * |
| * @param failureCode {@link WifiMetrics.ConnectionEvent} failure code. |
| * @param bssid the failed network. |
| * @param ssid identifies the failed network. |
| */ |
| public void handleConnectionAttemptEnded(@NonNull ActiveModeManager activeModeManager, |
| int failureCode, @NonNull String bssid, @NonNull String ssid) { |
| if (!mClientModeManagers.contains(activeModeManager)) { |
| Log.w(TAG, "Ignoring call from non primary Mode Manager" |
| + activeModeManager.getRole(), new Throwable()); |
| return; |
| } |
| if (failureCode == WifiMetrics.ConnectionEvent.FAILURE_NONE) { |
| String ssidUnquoted = (mWifiInfo.getWifiSsid() == null) |
| ? null |
| : mWifiInfo.getWifiSsid().toString(); |
| mOpenNetworkNotifier.handleWifiConnected(ssidUnquoted); |
| } else { |
| mOpenNetworkNotifier.handleConnectionFailure(); |
| retryConnectionOnLatestCandidates(bssid, ssid); |
| } |
| } |
| |
| private void retryConnectionOnLatestCandidates(String bssid, String ssid) { |
| try { |
| if (mLatestCandidates == null || mLatestCandidates.size() == 0 |
| || mClock.getElapsedSinceBootMillis() - mLatestCandidatesTimestampMs |
| > TEMP_BSSID_BLOCK_DURATION) { |
| mLatestCandidates = null; |
| return; |
| } |
| MacAddress macAddress = MacAddress.fromString(bssid); |
| int prevNumCandidates = mLatestCandidates.size(); |
| mLatestCandidates = mLatestCandidates.stream() |
| .filter(candidate -> !macAddress.equals(candidate.getKey().bssid)) |
| .collect(Collectors.toList()); |
| if (prevNumCandidates == mLatestCandidates.size()) { |
| return; |
| } |
| WifiConfiguration candidate = mNetworkSelector.selectNetwork(mLatestCandidates); |
| if (candidate != null) { |
| localLog("Automatic retry on the next best WNS candidate-" + candidate.SSID); |
| // Make sure that the failed BSSID is blocked for at least TEMP_BSSID_BLOCK_DURATION |
| // to prevent the supplicant from trying it again. |
| mBssidBlocklistMonitor.blockBssidForDurationMs(bssid, ssid, |
| TEMP_BSSID_BLOCK_DURATION, |
| BssidBlocklistMonitor.REASON_FRAMEWORK_DISCONNECT_FAST_RECONNECT, 0); |
| connectToNetwork(candidate); |
| } |
| } catch (IllegalArgumentException e) { |
| localLog("retryConnectionOnLatestCandidates: failed to create MacAddress from bssid=" |
| + bssid); |
| mLatestCandidates = null; |
| return; |
| } |
| } |
| |
| // Enable auto-join if WifiConnectivityManager is enabled & we have any pending generic network |
| // request (trusted or untrusted) and no specific network request in progress. |
| private void checkAllStatesAndEnableAutoJoin() { |
| // if auto-join was disabled externally, don't re-enable for any triggers. |
| // External triggers to disable always trumps any internal state. |
| setAutoJoinEnabled(mAutoJoinEnabledExternal |
| && (mUntrustedConnectionAllowed || mTrustedConnectionAllowed) |
| && !mSpecificNetworkRequestInProgress); |
| startConnectivityScan(SCAN_IMMEDIATELY); |
| } |
| |
| /** |
| * Triggered when {@link WifiNetworkFactory} has a pending general network request. |
| */ |
| public void setTrustedConnectionAllowed(boolean allowed) { |
| localLog("setTrustedConnectionAllowed: allowed=" + allowed); |
| |
| if (mTrustedConnectionAllowed != allowed) { |
| mTrustedConnectionAllowed = allowed; |
| checkAllStatesAndEnableAutoJoin(); |
| } |
| } |
| |
| |
| /** |
| * Triggered when {@link UntrustedWifiNetworkFactory} has a pending ephemeral network request. |
| */ |
| public void setUntrustedConnectionAllowed(boolean allowed) { |
| localLog("setUntrustedConnectionAllowed: allowed=" + allowed); |
| |
| if (mUntrustedConnectionAllowed != allowed) { |
| mUntrustedConnectionAllowed = allowed; |
| checkAllStatesAndEnableAutoJoin(); |
| } |
| } |
| |
| /** |
| * Triggered when {@link WifiNetworkFactory} is processing a specific network request. |
| */ |
| public void setSpecificNetworkRequestInProgress(boolean inProgress) { |
| localLog("setsetSpecificNetworkRequestInProgress : inProgress=" + inProgress); |
| |
| if (mSpecificNetworkRequestInProgress != inProgress) { |
| mSpecificNetworkRequestInProgress = inProgress; |
| checkAllStatesAndEnableAutoJoin(); |
| } |
| } |
| |
| /** |
| * Handler to prepare for connection to a user or app specified network |
| */ |
| public void prepareForForcedConnection(int netId) { |
| WifiConfiguration config = mConfigManager.getConfiguredNetwork(netId); |
| if (config == null) { |
| return; |
| } |
| localLog("prepareForForcedConnection: SSID=" + config.SSID); |
| |
| clearConnectionAttemptTimeStamps(); |
| mBssidBlocklistMonitor.clearBssidBlocklistForSsid(config.SSID); |
| } |
| |
| /** |
| * Handler for on-demand connectivity scan |
| */ |
| public void forceConnectivityScan(WorkSource workSource) { |
| if (!mWifiEnabled) return; |
| localLog("forceConnectivityScan in request of " + workSource); |
| |
| clearConnectionAttemptTimeStamps(); |
| mWaitForFullBandScanResults = true; |
| startForcedSingleScan(true, workSource); |
| } |
| |
| /** |
| * Helper method to populate WifiScanner handle. This is done lazily because |
| * WifiScanningService is started after WifiService. |
| */ |
| private void retrieveWifiScanner() { |
| if (mScanner != null) return; |
| mScanner = Objects.requireNonNull(mContext.getSystemService(WifiScanner.class), |
| "Got a null instance of WifiScanner!"); |
| // Register for all single scan results |
| mScanner.registerScanListener(new HandlerExecutor(mEventHandler), mAllSingleScanListener); |
| } |
| |
| /** |
| * Start WifiConnectivityManager |
| */ |
| private void start() { |
| if (mRunning) return; |
| retrieveWifiScanner(); |
| mConnectivityHelper.getFirmwareRoamingInfo(); |
| mBssidBlocklistMonitor.clearBssidBlocklist(); |
| mWifiChannelUtilization.init(getClientModeManager().getWifiLinkLayerStats()); |
| |
| if (mContext.getResources().getBoolean(R.bool.config_wifiEnablePartialInitialScan)) { |
| setInitialScanState(INITIAL_SCAN_STATE_START); |
| } |
| |
| mRunning = true; |
| mLatestCandidates = null; |
| mLatestCandidatesTimestampMs = 0; |
| } |
| |
| /** |
| * Stop and reset WifiConnectivityManager |
| */ |
| private void stop() { |
| if (!mRunning) return; |
| mRunning = false; |
| stopConnectivityScan(); |
| resetLastPeriodicSingleScanTimeStamp(); |
| mOpenNetworkNotifier.clearPendingNotification(true /* resetRepeatDelay */); |
| mLastConnectionAttemptBssid = null; |
| mWaitForFullBandScanResults = false; |
| mLatestCandidates = null; |
| mLatestCandidatesTimestampMs = 0; |
| mScanRestartCount = 0; |
| } |
| |
| /** |
| * Update WifiConnectivityManager running state |
| * |
| * Start WifiConnectivityManager only if both Wifi and WifiConnectivityManager |
| * are enabled, otherwise stop it. |
| */ |
| private void updateRunningState() { |
| if (mWifiEnabled && mAutoJoinEnabled) { |
| localLog("Starting up WifiConnectivityManager"); |
| start(); |
| } else { |
| localLog("Stopping WifiConnectivityManager"); |
| stop(); |
| } |
| } |
| |
| /** |
| * Inform WiFi is enabled for connection or not |
| */ |
| private void setWifiEnabled(boolean enable) { |
| if (mWifiEnabled == enable) return; |
| |
| localLog("Set WiFi " + (enable ? "enabled" : "disabled")); |
| |
| if (mWifiEnabled && !enable) { |
| mNetworkSelector.resetOnDisable(); |
| mBssidBlocklistMonitor.clearBssidBlocklist(); |
| } |
| mWifiEnabled = enable; |
| updateRunningState(); |
| } |
| |
| /** |
| * Turn on/off the WifiConnectivityManager at runtime |
| */ |
| private void setAutoJoinEnabled(boolean enable) { |
| mAutoJoinEnabled = enable; |
| updateRunningState(); |
| } |
| |
| /** |
| * Turn on/off the auto join at runtime |
| */ |
| public void setAutoJoinEnabledExternal(boolean enable) { |
| localLog("Set auto join " + (enable ? "enabled" : "disabled")); |
| |
| if (mAutoJoinEnabledExternal != enable) { |
| mAutoJoinEnabledExternal = enable; |
| checkAllStatesAndEnableAutoJoin(); |
| } |
| } |
| |
| @VisibleForTesting |
| int getLowRssiNetworkRetryDelay() { |
| return mPnoScanListener.getLowRssiNetworkRetryDelay(); |
| } |
| |
| @VisibleForTesting |
| long getLastPeriodicSingleScanTimeStamp() { |
| return mLastPeriodicSingleScanTimeStamp; |
| } |
| |
| /** |
| * Dump the local logs. |
| */ |
| public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { |
| pw.println("Dump of WifiConnectivityManager"); |
| pw.println("WifiConnectivityManager - Log Begin ----"); |
| mLocalLog.dump(fd, pw, args); |
| pw.println("WifiConnectivityManager - Log End ----"); |
| mOpenNetworkNotifier.dump(fd, pw, args); |
| mBssidBlocklistMonitor.dump(fd, pw, args); |
| } |
| } |