• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2016 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.server.wifi;
18 
19 import static android.net.wifi.WifiConfiguration.RANDOMIZATION_NONE;
20 
21 import static com.android.server.wifi.ActiveModeManager.ROLE_CLIENT_PRIMARY;
22 import static com.android.server.wifi.ActiveModeManager.ROLE_CLIENT_SECONDARY_LONG_LIVED;
23 import static com.android.server.wifi.ActiveModeManager.ROLE_CLIENT_SECONDARY_TRANSIENT;
24 import static com.android.server.wifi.ClientModeImpl.WIFI_WORK_SOURCE;
25 
26 import android.annotation.NonNull;
27 import android.annotation.Nullable;
28 import android.app.AlarmManager;
29 import android.content.BroadcastReceiver;
30 import android.content.Context;
31 import android.content.Intent;
32 import android.content.IntentFilter;
33 import android.net.MacAddress;
34 import android.net.wifi.ScanResult;
35 import android.net.wifi.WifiConfiguration;
36 import android.net.wifi.WifiInfo;
37 import android.net.wifi.WifiManager;
38 import android.net.wifi.WifiManager.DeviceMobilityState;
39 import android.net.wifi.WifiNetworkSuggestion;
40 import android.net.wifi.WifiScanner;
41 import android.net.wifi.WifiScanner.PnoSettings;
42 import android.net.wifi.WifiScanner.ScanSettings;
43 import android.net.wifi.hotspot2.PasspointConfiguration;
44 import android.os.Handler;
45 import android.os.HandlerExecutor;
46 import android.os.PowerManager;
47 import android.os.Process;
48 import android.os.WorkSource;
49 import android.util.ArrayMap;
50 import android.util.LocalLog;
51 import android.util.Log;
52 
53 import com.android.internal.annotations.GuardedBy;
54 import com.android.internal.annotations.VisibleForTesting;
55 import com.android.modules.utils.build.SdkLevel;
56 import com.android.server.wifi.hotspot2.PasspointManager;
57 import com.android.server.wifi.util.ScanResultUtil;
58 import com.android.wifi.resources.R;
59 
60 import java.io.FileDescriptor;
61 import java.io.PrintWriter;
62 import java.util.ArrayList;
63 import java.util.Collections;
64 import java.util.HashSet;
65 import java.util.Iterator;
66 import java.util.LinkedList;
67 import java.util.List;
68 import java.util.Map;
69 import java.util.Objects;
70 import java.util.Set;
71 import java.util.stream.Collectors;
72 import java.util.stream.Stream;
73 
74 /**
75  * This class manages all the connectivity related scanning activities.
76  *
77  * When the screen is turned on or off, WiFi is connected or disconnected,
78  * or on-demand, a scan is initiatiated and the scan results are passed
79  * to WifiNetworkSelector for it to make a recommendation on which network
80  * to connect to.
81  */
82 public class WifiConnectivityManager {
83     public static final String WATCHDOG_TIMER_TAG =
84             "WifiConnectivityManager Schedule Watchdog Timer";
85     public static final String PERIODIC_SCAN_TIMER_TAG =
86             "WifiConnectivityManager Schedule Periodic Scan Timer";
87     public static final String RESTART_SINGLE_SCAN_TIMER_TAG =
88             "WifiConnectivityManager Restart Single Scan";
89     public static final String RESTART_CONNECTIVITY_SCAN_TIMER_TAG =
90             "WifiConnectivityManager Restart Scan";
91     public static final String DELAYED_PARTIAL_SCAN_TIMER_TAG =
92             "WifiConnectivityManager Schedule Delayed Partial Scan Timer";
93 
94     private static final long RESET_TIME_STAMP = Long.MIN_VALUE;
95     // Constants to indicate whether a scan should start immediately or
96     // it should comply to the minimum scan interval rule.
97     private static final boolean SCAN_IMMEDIATELY = true;
98     private static final boolean SCAN_ON_SCHEDULE = false;
99 
100     // PNO scan interval in milli-seconds. This is the scan
101     // performed when screen is off and connected.
102     private static final int CONNECTED_PNO_SCAN_INTERVAL_MS = 160 * 1000; // 160 seconds
103     // Maximum number of retries when starting a scan failed
104     @VisibleForTesting
105     public static final int MAX_SCAN_RESTART_ALLOWED = 5;
106     // Number of milli-seconds to delay before retry starting
107     // a previously failed scan
108     private static final int RESTART_SCAN_DELAY_MS = 2 * 1000; // 2 seconds
109     // Restricted channel list age out value.
110     private static final long CHANNEL_LIST_AGE_MS = 60 * 60 * 1000; // 1 hour
111     // This is the time interval for the connection attempt rate calculation. Connection attempt
112     // timestamps beyond this interval is evicted from the list.
113     public static final int MAX_CONNECTION_ATTEMPTS_TIME_INTERVAL_MS = 4 * 60 * 1000; // 4 mins
114     // Max number of connection attempts in the above time interval.
115     public static final int MAX_CONNECTION_ATTEMPTS_RATE = 6;
116     private static final int TEMP_BSSID_BLOCK_DURATION = 10 * 1000; // 10 seconds
117     // Maximum age of frequencies last seen to be included in pno scans. (30 days)
118     private static final long MAX_PNO_SCAN_FREQUENCY_AGE_MS = (long) 1000 * 3600 * 24 * 30;
119     private static final int POWER_SAVE_SCAN_INTERVAL_MULTIPLIER = 2;
120     // ClientModeManager has a bunch of states. From the
121     // WifiConnectivityManager's perspective it only cares
122     // if it is in Connected state, Disconnected state or in
123     // transition between these two states.
124     public static final int WIFI_STATE_UNKNOWN = 0;
125     public static final int WIFI_STATE_CONNECTED = 1;
126     public static final int WIFI_STATE_DISCONNECTED = 2;
127     public static final int WIFI_STATE_TRANSITIONING = 3;
128 
129     // Initial scan state, used to manage performing partial scans in initial scans
130     // Initial scans are the first scan after enabling Wifi or turning on screen when disconnected
131     @VisibleForTesting
132     public static final int INITIAL_SCAN_STATE_START = 0;
133     public static final int INITIAL_SCAN_STATE_AWAITING_RESPONSE = 1;
134     public static final int INITIAL_SCAN_STATE_COMPLETE = 2;
135 
136     // Log tag for this class
137     private static final String TAG = "WifiConnectivityManager";
138     private static final String ALL_SINGLE_SCAN_LISTENER = "AllSingleScanListener";
139     private static final String PNO_SCAN_LISTENER = "PnoScanListener";
140 
141     private final WifiContext mContext;
142     private final WifiConfigManager mConfigManager;
143     private final WifiNetworkSuggestionsManager mWifiNetworkSuggestionsManager;
144     private final WifiConnectivityHelper mConnectivityHelper;
145     private final WifiNetworkSelector mNetworkSelector;
146     private final WifiLastResortWatchdog mWifiLastResortWatchdog;
147     private final OpenNetworkNotifier mOpenNetworkNotifier;
148     private final WifiMetrics mWifiMetrics;
149     private final AlarmManager mAlarmManager;
150     private final Handler mEventHandler;
151     private final Clock mClock;
152     private final ScoringParams mScoringParams;
153     private final LocalLog mLocalLog;
154     private final WifiGlobals mWifiGlobals;
155     /**
156      * Keeps connection attempts within the last {@link #MAX_CONNECTION_ATTEMPTS_TIME_INTERVAL_MS}
157      * milliseconds.
158      */
159     private final LinkedList<Long> mConnectionAttemptTimeStamps = new LinkedList<>();
160     private final WifiBlocklistMonitor mWifiBlocklistMonitor;
161     private final PasspointManager mPasspointManager;
162     private final WifiScoreCard mWifiScoreCard;
163     private final WifiChannelUtilization mWifiChannelUtilization;
164     private final PowerManager mPowerManager;
165     private final DeviceConfigFacade mDeviceConfigFacade;
166     private final ActiveModeWarden mActiveModeWarden;
167 
168     private WifiScanner mScanner;
169     private boolean mDbg = false;
170     private boolean mVerboseLoggingEnabled = false;
171     private boolean mWifiEnabled = false;
172     private boolean mAutoJoinEnabled = false; // disabled by default, enabled by external triggers
173     private boolean mRunning = false;
174     private boolean mScreenOn = false;
175     private int mWifiState = WIFI_STATE_UNKNOWN;
176     private int mInitialScanState = INITIAL_SCAN_STATE_COMPLETE;
177     private boolean mAutoJoinEnabledExternal = true; // enabled by default
178     private boolean mUntrustedConnectionAllowed = false;
179     private boolean mOemPaidConnectionAllowed = false;
180     private boolean mOemPrivateConnectionAllowed = false;
181     private WorkSource mOemPaidConnectionRequestorWs = null;
182     private WorkSource mOemPrivateConnectionRequestorWs = null;
183     private boolean mTrustedConnectionAllowed = false;
184     private boolean mSpecificNetworkRequestInProgress = false;
185     private int mScanRestartCount = 0;
186     private int mSingleScanRestartCount = 0;
187     private int mTotalConnectivityAttemptsRateLimited = 0;
188     private long mLastPeriodicSingleScanTimeStamp = RESET_TIME_STAMP;
189     private long mLastNetworkSelectionTimeStamp = RESET_TIME_STAMP;
190     private boolean mPnoScanStarted = false;
191     private boolean mPeriodicScanTimerSet = false;
192     private boolean mDelayedPartialScanTimerSet = false;
193     private boolean mWatchdogScanTimerSet = false;
194 
195     // Used for Initial Scan metrics
196     private boolean mFailedInitialPartialScan = false;
197     private int mInitialPartialScanChannelCount;
198 
199     // Device configs
200     private boolean mWaitForFullBandScanResults = false;
201 
202     // Scanning Schedules
203     // Default schedule used in case of invalid configuration
204     private static final int[] DEFAULT_SCANNING_SCHEDULE_SEC = {20, 40, 80, 160};
205     private int[] mConnectedSingleScanScheduleSec;
206     private int[] mDisconnectedSingleScanScheduleSec;
207     private int[] mConnectedSingleSavedNetworkSingleScanScheduleSec;
208     private List<WifiCandidates.Candidate> mLatestCandidates = null;
209     private long mLatestCandidatesTimestampMs = 0;
210 
211     private final Object mLock = new Object();
212 
213     @GuardedBy("mLock")
214     private int[] mCurrentSingleScanScheduleSec;
215 
216     private int mCurrentSingleScanScheduleIndex;
217     // Cached WifiCandidates used in high mobility state to avoid connecting to APs that are
218     // moving relative to the user.
219     private CachedWifiCandidates mCachedWifiCandidates = null;
220     private @DeviceMobilityState int mDeviceMobilityState =
221             WifiManager.DEVICE_MOBILITY_STATE_UNKNOWN;
222 
223     // A helper to log debugging information in the local log buffer, which can
224     // be retrieved in bugreport.
localLog(String log)225     private void localLog(String log) {
226         mLocalLog.log(log);
227         if (mVerboseLoggingEnabled) Log.v(TAG, log);
228     }
229 
230     /**
231      * Enable verbose logging for WifiConnectivityManager.
232      */
enableVerboseLogging(boolean verbose)233     public void enableVerboseLogging(boolean verbose) {
234         mVerboseLoggingEnabled = verbose;
235     }
236 
237     // A periodic/PNO scan will be rescheduled up to MAX_SCAN_RESTART_ALLOWED times
238     // if the start scan command failed. A timer is used here to make it a deferred retry.
239     private final AlarmManager.OnAlarmListener mRestartScanListener =
240             new AlarmManager.OnAlarmListener() {
241                 public void onAlarm() {
242                     startConnectivityScan(SCAN_IMMEDIATELY);
243                 }
244             };
245 
246     // A single scan will be rescheduled up to MAX_SCAN_RESTART_ALLOWED times
247     // if the start scan command failed. An timer is used here to make it a deferred retry.
248     private class RestartSingleScanListener implements AlarmManager.OnAlarmListener {
249         private final boolean mIsFullBandScan;
250 
RestartSingleScanListener(boolean isFullBandScan)251         RestartSingleScanListener(boolean isFullBandScan) {
252             mIsFullBandScan = isFullBandScan;
253         }
254 
255         @Override
onAlarm()256         public void onAlarm() {
257             startSingleScan(mIsFullBandScan, WIFI_WORK_SOURCE);
258         }
259     }
260 
261     // As a watchdog mechanism, a single scan will be scheduled every
262     // config_wifiPnoWatchdogIntervalMinutes if it is in the WIFI_STATE_DISCONNECTED state.
263     private final AlarmManager.OnAlarmListener mWatchdogListener =
264             new AlarmManager.OnAlarmListener() {
265                 public void onAlarm() {
266                     watchdogHandler();
267                 }
268             };
269 
270     // Due to b/28020168, timer based single scan will be scheduled
271     // to provide periodic scan in an exponential backoff fashion.
272     private final AlarmManager.OnAlarmListener mPeriodicScanTimerListener =
273             new AlarmManager.OnAlarmListener() {
274                 public void onAlarm() {
275                     periodicScanTimerHandler();
276                 }
277             };
278 
279     private final AlarmManager.OnAlarmListener mDelayedPartialScanTimerListener =
280             new AlarmManager.OnAlarmListener() {
281                 public void onAlarm() {
282                     if (mCachedWifiCandidates == null
283                             || mCachedWifiCandidates.frequencies == null
284                             || mCachedWifiCandidates.frequencies.size() == 0) {
285                         return;
286                     }
287                     ScanSettings settings = new ScanSettings();
288                     settings.type = WifiScanner.SCAN_TYPE_HIGH_ACCURACY;
289                     settings.band = getScanBand(false);
290                     settings.reportEvents = WifiScanner.REPORT_EVENT_FULL_SCAN_RESULT
291                             | WifiScanner.REPORT_EVENT_AFTER_EACH_SCAN;
292                     settings.numBssidsPerScan = 0;
293                     int index = 0;
294                     settings.channels =
295                             new WifiScanner.ChannelSpec[mCachedWifiCandidates.frequencies.size()];
296                     for (Integer freq : mCachedWifiCandidates.frequencies) {
297                         settings.channels[index++] = new WifiScanner.ChannelSpec(freq);
298                     }
299                     SingleScanListener singleScanListener = new SingleScanListener(false);
300                     mScanner.startScan(settings, new HandlerExecutor(mEventHandler),
301                             singleScanListener, WIFI_WORK_SOURCE);
302                     mWifiMetrics.incrementConnectivityOneshotScanCount();
303                 }
304             };
305 
306     /**
307      * Interface for callback from handling scan results.
308      */
309     private interface HandleScanResultsListener {
310         /**
311          * @param wasCandidateSelected true - if a candidate is selected by WifiNetworkSelector
312          *                             false - if no candidate is selected by WifiNetworkSelector
313          */
onHandled(boolean wasCandidateSelected)314         void onHandled(boolean wasCandidateSelected);
315     }
316 
317     /**
318      * Helper method to consolidate handling of scan results when no candidate is selected.
319      */
handleScanResultsWithNoCandidate( @onNull HandleScanResultsListener handleScanResultsListener)320     private void handleScanResultsWithNoCandidate(
321             @NonNull HandleScanResultsListener handleScanResultsListener) {
322         if (mWifiState == WIFI_STATE_DISCONNECTED) {
323             mOpenNetworkNotifier.handleScanResults(
324                     mNetworkSelector.getFilteredScanDetailsForOpenUnsavedNetworks());
325         }
326         mWifiMetrics.noteFirstNetworkSelectionAfterBoot(false);
327         handleScanResultsListener.onHandled(false);
328     }
329 
330     /**
331      * Helper method to consolidate handling of scan results when a candidate is selected.
332      */
handleScanResultsWithCandidate( @onNull HandleScanResultsListener handleScanResultsListener)333     private void handleScanResultsWithCandidate(
334             @NonNull HandleScanResultsListener handleScanResultsListener) {
335         mWifiMetrics.noteFirstNetworkSelectionAfterBoot(true);
336         handleScanResultsListener.onHandled(true);
337     }
338 
339     /**
340      * Handles 'onResult' callbacks for the Periodic, Single & Pno ScanListener.
341      * Executes selection of potential network candidates, initiation of connection attempt to that
342      * network.
343      */
handleScanResults(@onNull List<ScanDetail> scanDetails, @NonNull String listenerName, boolean isFullScan, @NonNull HandleScanResultsListener handleScanResultsListener)344     private void handleScanResults(@NonNull List<ScanDetail> scanDetails,
345             @NonNull String listenerName,
346             boolean isFullScan,
347             @NonNull HandleScanResultsListener handleScanResultsListener) {
348         List<WifiNetworkSelector.ClientModeManagerState> cmmStates = new ArrayList<>();
349         Set<String> connectedSsids = new HashSet<>();
350         boolean hasExistingSecondaryCmm = false;
351         for (ClientModeManager clientModeManager :
352                 mActiveModeWarden.getInternetConnectivityClientModeManagers()) {
353             if (clientModeManager.getRole() == ROLE_CLIENT_SECONDARY_LONG_LIVED) {
354                 hasExistingSecondaryCmm = true;
355             }
356             mWifiChannelUtilization.refreshChannelStatsAndChannelUtilization(
357                     clientModeManager.getWifiLinkLayerStats(),
358                     WifiChannelUtilization.UNKNOWN_FREQ);
359             WifiInfo wifiInfo = clientModeManager.syncRequestConnectionInfo();
360             if (clientModeManager.isConnected()) {
361                 connectedSsids.add(wifiInfo.getSSID());
362             }
363             cmmStates.add(new WifiNetworkSelector.ClientModeManagerState(clientModeManager));
364         }
365         // We don't have any existing secondary CMM, but are we allowed to create a secondary CMM
366         // and do we have a request for OEM_PAID/OEM_PRIVATE request? If yes, we need to perform
367         // network selection to check if we have any potential candidate for the secondary CMM
368         // creation.
369         if (!hasExistingSecondaryCmm
370                 && (mOemPaidConnectionAllowed || mOemPrivateConnectionAllowed)) {
371             // prefer OEM PAID requestor if it exists.
372             WorkSource oemPaidOrOemPrivateRequestorWs =
373                     mOemPaidConnectionRequestorWs != null
374                             ? mOemPaidConnectionRequestorWs
375                             : mOemPrivateConnectionRequestorWs;
376             if (oemPaidOrOemPrivateRequestorWs == null) {
377                 Log.e(TAG, "Both mOemPaidConnectionRequestorWs & mOemPrivateConnectionRequestorWs "
378                         + "are null!");
379             }
380             if (oemPaidOrOemPrivateRequestorWs != null
381                     && mActiveModeWarden.canRequestMoreClientModeManagersInRole(
382                             oemPaidOrOemPrivateRequestorWs,
383                             ROLE_CLIENT_SECONDARY_LONG_LIVED)) {
384                 // Add a placeholder CMM state to ensure network selection is performed for a
385                 // potential second STA creation.
386                 cmmStates.add(new WifiNetworkSelector.ClientModeManagerState());
387             }
388         }
389         // Check if any blocklisted BSSIDs can be freed.
390         mWifiBlocklistMonitor.tryEnablingBlockedBssids(scanDetails);
391         Set<String> bssidBlocklist = mWifiBlocklistMonitor.updateAndGetBssidBlocklistForSsids(
392                 connectedSsids);
393         updateUserDisabledList(scanDetails);
394         // Clear expired recent failure statuses
395         mConfigManager.cleanupExpiredRecentFailureReasons();
396 
397         localLog(listenerName + " onResults: start network selection");
398 
399         List<WifiCandidates.Candidate> candidates = mNetworkSelector.getCandidatesFromScan(
400                 scanDetails, bssidBlocklist, cmmStates, mUntrustedConnectionAllowed,
401                 mOemPaidConnectionAllowed, mOemPrivateConnectionAllowed);
402         mLatestCandidates = candidates;
403         mLatestCandidatesTimestampMs = mClock.getElapsedSinceBootMillis();
404 
405         if (mDeviceMobilityState == WifiManager.DEVICE_MOBILITY_STATE_HIGH_MVMT
406                 && mContext.getResources().getBoolean(
407                         R.bool.config_wifiHighMovementNetworkSelectionOptimizationEnabled)) {
408             candidates = filterCandidatesHighMovement(candidates, listenerName, isFullScan);
409         }
410 
411         mLastNetworkSelectionTimeStamp = mClock.getElapsedSinceBootMillis();
412         mWifiLastResortWatchdog.updateAvailableNetworks(
413                 mNetworkSelector.getConnectableScanDetails());
414         mWifiMetrics.countScanResults(scanDetails);
415         // No candidates, return early.
416         if (candidates == null || candidates.size() == 0) {
417             localLog(listenerName + ":  No candidates");
418             handleScanResultsWithNoCandidate(handleScanResultsListener);
419             return;
420         }
421         // We have an oem paid/private network request and device supports STA + STA, check if there
422         // are oem paid/private suggestions.
423         if ((mOemPaidConnectionAllowed || mOemPrivateConnectionAllowed)
424                 && mActiveModeWarden.isStaStaConcurrencySupportedForRestrictedConnections()) {
425             // Split the candidates based on whether they are oem paid/oem private or not.
426             Map<Boolean, List<WifiCandidates.Candidate>> candidatesPartitioned =
427                     candidates.stream()
428                             .collect(Collectors.groupingBy(c -> c.isOemPaid() || c.isOemPrivate()));
429             List<WifiCandidates.Candidate> primaryCmmCandidates =
430                     candidatesPartitioned.getOrDefault(false, Collections.emptyList());
431             List<WifiCandidates.Candidate> secondaryCmmCandidates =
432                     candidatesPartitioned.getOrDefault(true, Collections.emptyList());
433             // Some oem paid/private suggestions found, use secondary cmm flow.
434             if (!secondaryCmmCandidates.isEmpty()) {
435                 handleCandidatesFromScanResultsUsingSecondaryCmmIfAvailable(
436                         listenerName, primaryCmmCandidates, secondaryCmmCandidates,
437                         handleScanResultsListener);
438                 return;
439             }
440             // intentional fallthrough: No oem paid/private suggestions, fallback to legacy flow.
441         }
442         handleCandidatesFromScanResultsForPrimaryCmmUsingMbbIfAvailable(
443                 listenerName, candidates, handleScanResultsListener);
444     }
445 
446     /**
447      * Executes selection of best network for 2 concurrent STA's from the candidates provided,
448      * initiation of connection attempt to a network on both the STA's (if found).
449      */
handleCandidatesFromScanResultsUsingSecondaryCmmIfAvailable( @onNull String listenerName, @NonNull List<WifiCandidates.Candidate> primaryCmmCandidates, @NonNull List<WifiCandidates.Candidate> secondaryCmmCandidates, @NonNull HandleScanResultsListener handleScanResultsListener)450     private void handleCandidatesFromScanResultsUsingSecondaryCmmIfAvailable(
451             @NonNull String listenerName,
452             @NonNull List<WifiCandidates.Candidate> primaryCmmCandidates,
453             @NonNull List<WifiCandidates.Candidate> secondaryCmmCandidates,
454             @NonNull HandleScanResultsListener handleScanResultsListener) {
455         // Perform network selection among secondary candidates.
456         WifiConfiguration secondaryCmmCandidate =
457                 mNetworkSelector.selectNetwork(secondaryCmmCandidates);
458         // No oem paid/private selected, fallback to legacy flow (should never happen!).
459         if (secondaryCmmCandidate == null
460                 || secondaryCmmCandidate.getNetworkSelectionStatus().getCandidate() == null) {
461             localLog(listenerName + ": No secondary candidate");
462             handleCandidatesFromScanResultsForPrimaryCmmUsingMbbIfAvailable(
463                     listenerName,
464                     Stream.concat(primaryCmmCandidates.stream(), secondaryCmmCandidates.stream())
465                             .collect(Collectors.toList()),
466                     handleScanResultsListener);
467             return;
468         }
469         String secondaryCmmCandidateBssid =
470                 secondaryCmmCandidate.getNetworkSelectionStatus().getCandidate().BSSID;
471         WorkSource secondaryRequestorWs = null;
472         // OEM_PAID takes precedence over OEM_PRIVATE, so attribute to OEM_PAID requesting app.
473         if (secondaryCmmCandidate.oemPaid
474                 && mActiveModeWarden.canRequestMoreClientModeManagersInRole(
475                 mOemPaidConnectionRequestorWs, ROLE_CLIENT_SECONDARY_LONG_LIVED)) {
476             secondaryRequestorWs = mOemPaidConnectionRequestorWs;
477         } else if (secondaryCmmCandidate.oemPrivate
478                 && mActiveModeWarden.canRequestMoreClientModeManagersInRole(
479                 mOemPrivateConnectionRequestorWs, ROLE_CLIENT_SECONDARY_LONG_LIVED)) {
480             secondaryRequestorWs = mOemPrivateConnectionRequestorWs;
481         }
482         // Secondary STA not available, fallback to legacy flow.
483         if (secondaryRequestorWs == null) {
484             localLog(listenerName + ": No secondary STA available");
485             handleCandidatesFromScanResultsForPrimaryCmmUsingMbbIfAvailable(
486                     listenerName,
487                     Stream.concat(primaryCmmCandidates.stream(), secondaryCmmCandidates.stream())
488                             .collect(Collectors.toList()),
489                     handleScanResultsListener);
490             return;
491         }
492         WifiConfiguration primaryCmmCandidate =
493                 mNetworkSelector.selectNetwork(primaryCmmCandidates);
494         // Request for a new client mode manager to spin up concurrent connection
495         mActiveModeWarden.requestSecondaryLongLivedClientModeManager(
496                 (cm) -> {
497                     if (cm == null) {
498                         localLog(listenerName + ": Secondary client mode manager request returned "
499                                 + "null, aborting (wifi off?)");
500                         handleScanResultsWithNoCandidate(handleScanResultsListener);
501                         return;
502                     }
503                     // We did not end up getting the secondary client mode manager for some reason
504                     // after we checked above! Fallback to legacy flow.
505                     if (cm.getRole() == ROLE_CLIENT_PRIMARY) {
506                         localLog(listenerName + ": Secondary client mode manager request returned"
507                                 + " primary, falling back to single client mode manager flow.");
508                         handleCandidatesFromScanResultsForPrimaryCmmUsingMbbIfAvailable(
509                                 listenerName,
510                                 Stream.concat(primaryCmmCandidates.stream(),
511                                         secondaryCmmCandidates.stream())
512                                         .collect(Collectors.toList()),
513                                 handleScanResultsListener);
514                         return;
515                     }
516                     // Don't use make before break for these connection requests.
517 
518                     // If we also selected a primary candidate trigger connection.
519                     if (primaryCmmCandidate != null) {
520                         localLog(listenerName + ":  WNS candidate(primary)-"
521                                 + primaryCmmCandidate.SSID);
522                         connectToNetworkUsingCmmWithoutMbb(
523                                 getPrimaryClientModeManager(), primaryCmmCandidate);
524                     }
525 
526                     localLog(listenerName + ":  WNS candidate(secondary)-"
527                             + secondaryCmmCandidate.SSID);
528                     // Secndary candidate cannot be null (otherwise we would have switched to legacy
529                     // flow above)
530                     connectToNetworkUsingCmmWithoutMbb(cm, secondaryCmmCandidate);
531 
532                     handleScanResultsWithCandidate(handleScanResultsListener);
533                 }, secondaryRequestorWs,
534                 secondaryCmmCandidate.SSID,
535                 mConnectivityHelper.isFirmwareRoamingSupported()
536                         ? null : secondaryCmmCandidateBssid);
537     }
538 
539     /**
540      * Executes selection of best network from the candidates provided, initiation of connection
541      * attempt to that network.
542      */
handleCandidatesFromScanResultsForPrimaryCmmUsingMbbIfAvailable( @onNull String listenerName, @NonNull List<WifiCandidates.Candidate> candidates, @NonNull HandleScanResultsListener handleScanResultsListener)543     private void handleCandidatesFromScanResultsForPrimaryCmmUsingMbbIfAvailable(
544             @NonNull String listenerName, @NonNull List<WifiCandidates.Candidate> candidates,
545             @NonNull HandleScanResultsListener handleScanResultsListener) {
546         WifiConfiguration candidate = mNetworkSelector.selectNetwork(candidates);
547         if (candidate != null) {
548             localLog(listenerName + ":  WNS candidate-" + candidate.SSID);
549             connectToNetworkForPrimaryCmmUsingMbbIfAvailable(candidate);
550             handleScanResultsWithCandidate(handleScanResultsListener);
551         } else {
552             localLog(listenerName + ":  No candidate");
553             handleScanResultsWithNoCandidate(handleScanResultsListener);
554         }
555     }
556 
filterCandidatesHighMovement( List<WifiCandidates.Candidate> candidates, String listenerName, boolean isFullScan)557     private List<WifiCandidates.Candidate> filterCandidatesHighMovement(
558             List<WifiCandidates.Candidate> candidates, String listenerName, boolean isFullScan) {
559         boolean isNotPartialScan = isFullScan || listenerName.equals(PNO_SCAN_LISTENER);
560         if (candidates == null || candidates.isEmpty()) {
561             // No connectable networks nearby or network selection is unnecessary
562             if (isNotPartialScan) {
563                 mCachedWifiCandidates = new CachedWifiCandidates(mClock.getElapsedSinceBootMillis(),
564                         null);
565             }
566             return null;
567         }
568 
569         long minimumTimeBetweenScansMs = mContext.getResources().getInteger(
570                 R.integer.config_wifiHighMovementNetworkSelectionOptimizationScanDelayMs);
571         if (mCachedWifiCandidates != null && mCachedWifiCandidates.candidateRssiMap != null) {
572             // cached candidates are too recent, wait for next scan
573             if (mClock.getElapsedSinceBootMillis() - mCachedWifiCandidates.timeSinceBootMs
574                     < minimumTimeBetweenScansMs) {
575                 mWifiMetrics.incrementNumHighMovementConnectionSkipped();
576                 return null;
577             }
578 
579             int rssiDelta = mContext.getResources().getInteger(R.integer
580                     .config_wifiHighMovementNetworkSelectionOptimizationRssiDelta);
581             List<WifiCandidates.Candidate> filteredCandidates = candidates.stream().filter(
582                     item -> mCachedWifiCandidates.candidateRssiMap.containsKey(item.getKey())
583                             && Math.abs(mCachedWifiCandidates.candidateRssiMap.get(item.getKey())
584                             - item.getScanRssi()) < rssiDelta)
585                     .collect(Collectors.toList());
586 
587             if (!filteredCandidates.isEmpty()) {
588                 if (isNotPartialScan) {
589                     mCachedWifiCandidates =
590                             new CachedWifiCandidates(mClock.getElapsedSinceBootMillis(),
591                             candidates);
592                 }
593                 mWifiMetrics.incrementNumHighMovementConnectionStarted();
594                 return filteredCandidates;
595             }
596         }
597 
598         // Either no cached candidates, or all candidates got filtered out.
599         // Update the cached candidates here and schedule a delayed partial scan.
600         if (isNotPartialScan) {
601             mCachedWifiCandidates = new CachedWifiCandidates(mClock.getElapsedSinceBootMillis(),
602                     candidates);
603             localLog("Found " + candidates.size() + " candidates at high mobility state. "
604                     + "Re-doing scan to confirm network quality.");
605             scheduleDelayedPartialScan(minimumTimeBetweenScansMs);
606         }
607         mWifiMetrics.incrementNumHighMovementConnectionSkipped();
608         return null;
609     }
610 
updateUserDisabledList(List<ScanDetail> scanDetails)611     private void updateUserDisabledList(List<ScanDetail> scanDetails) {
612         List<String> results = new ArrayList<>();
613         List<ScanResult> passpointAp = new ArrayList<>();
614         for (ScanDetail scanDetail : scanDetails) {
615             results.add(ScanResultUtil.createQuotedSSID(scanDetail.getScanResult().SSID));
616             if (!scanDetail.getScanResult().isPasspointNetwork()) {
617                 continue;
618             }
619             passpointAp.add(scanDetail.getScanResult());
620         }
621         if (!passpointAp.isEmpty()) {
622             results.addAll(mPasspointManager
623                     .getAllMatchingPasspointProfilesForScanResults(passpointAp).keySet());
624         }
625         mConfigManager.updateUserDisabledList(results);
626     }
627 
628     private class CachedWifiCandidates {
629         public final long timeSinceBootMs;
630         public final Map<WifiCandidates.Key, Integer> candidateRssiMap;
631         public final Set<Integer> frequencies;
632 
CachedWifiCandidates(long timeSinceBootMs, List<WifiCandidates.Candidate> candidates)633         CachedWifiCandidates(long timeSinceBootMs, List<WifiCandidates.Candidate> candidates) {
634             this.timeSinceBootMs = timeSinceBootMs;
635             if (candidates == null) {
636                 this.candidateRssiMap = null;
637                 this.frequencies = null;
638             } else {
639                 this.candidateRssiMap = new ArrayMap<WifiCandidates.Key, Integer>();
640                 this.frequencies = new HashSet<Integer>();
641                 for (WifiCandidates.Candidate c : candidates) {
642                     candidateRssiMap.put(c.getKey(), c.getScanRssi());
643                     frequencies.add(c.getFrequency());
644                 }
645             }
646         }
647     }
648 
649     // All single scan results listener.
650     //
651     // Note: This is the listener for all the available single scan results,
652     //       including the ones initiated by WifiConnectivityManager and
653     //       other modules.
654     private class AllSingleScanListener implements WifiScanner.ScanListener {
655         private List<ScanDetail> mScanDetails = new ArrayList<ScanDetail>();
656         private int mNumScanResultsIgnoredDueToSingleRadioChain = 0;
657 
clearScanDetails()658         public void clearScanDetails() {
659             mScanDetails.clear();
660             mNumScanResultsIgnoredDueToSingleRadioChain = 0;
661         }
662 
663         @Override
onSuccess()664         public void onSuccess() {
665         }
666 
667         @Override
onFailure(int reason, String description)668         public void onFailure(int reason, String description) {
669             localLog("registerScanListener onFailure:"
670                       + " reason: " + reason + " description: " + description);
671         }
672 
673         @Override
onPeriodChanged(int periodInMs)674         public void onPeriodChanged(int periodInMs) {
675         }
676 
677         @Override
onResults(WifiScanner.ScanData[] results)678         public void onResults(WifiScanner.ScanData[] results) {
679             if (!mWifiEnabled || !mAutoJoinEnabled) {
680                 clearScanDetails();
681                 mWaitForFullBandScanResults = false;
682                 return;
683             }
684 
685             // We treat any full band scans (with DFS or not) as "full".
686             boolean isFullBandScanResults = false;
687             if (results != null && results.length > 0) {
688                 isFullBandScanResults =
689                         WifiScanner.isFullBandScan(results[0].getScannedBandsInternal(), true);
690             }
691             // Full band scan results only.
692             if (mWaitForFullBandScanResults) {
693                 if (!isFullBandScanResults) {
694                     localLog("AllSingleScanListener waiting for full band scan results.");
695                     clearScanDetails();
696                     return;
697                 } else {
698                     mWaitForFullBandScanResults = false;
699                 }
700             }
701 
702             // Create a new list to avoid looping call trigger concurrent exception.
703             List<ScanDetail> scanDetailList = new ArrayList<>(mScanDetails);
704             clearScanDetails();
705 
706             if (results != null && results.length > 0) {
707                 mWifiMetrics.incrementAvailableNetworksHistograms(scanDetailList,
708                         isFullBandScanResults);
709             }
710             if (mNumScanResultsIgnoredDueToSingleRadioChain > 0) {
711                 Log.i(TAG, "Number of scan results ignored due to single radio chain scan: "
712                         + mNumScanResultsIgnoredDueToSingleRadioChain);
713             }
714             handleScanResults(scanDetailList,
715                     ALL_SINGLE_SCAN_LISTENER, isFullBandScanResults,
716                     wasCandidateSelected -> {
717                         // Update metrics to see if a single scan detected a valid network
718                         // while PNO scan didn't.
719                         // Note: We don't update the background scan metrics any more as it is
720                         //       not in use.
721                         if (mPnoScanStarted) {
722                             if (wasCandidateSelected) {
723                                 mWifiMetrics.incrementNumConnectivityWatchdogPnoBad();
724                             } else {
725                                 mWifiMetrics.incrementNumConnectivityWatchdogPnoGood();
726                             }
727                         }
728 
729                         // Check if we are in the middle of initial partial scan
730                         if (mInitialScanState == INITIAL_SCAN_STATE_AWAITING_RESPONSE) {
731                             // Done with initial scan
732                             setInitialScanState(INITIAL_SCAN_STATE_COMPLETE);
733 
734                             if (wasCandidateSelected) {
735                                 Log.i(TAG, "Connection attempted with the reduced initial scans");
736                                 mWifiMetrics.reportInitialPartialScan(
737                                         mInitialPartialScanChannelCount, true);
738                                 mInitialPartialScanChannelCount = 0;
739                             } else {
740                                 Log.i(TAG, "Connection was not attempted, issuing a full scan");
741                                 startConnectivityScan(SCAN_IMMEDIATELY);
742                                 mFailedInitialPartialScan = true;
743                             }
744                         } else if (mInitialScanState == INITIAL_SCAN_STATE_COMPLETE) {
745                             if (mFailedInitialPartialScan && wasCandidateSelected) {
746                                 // Initial scan failed, but following full scan succeeded
747                                 mWifiMetrics.reportInitialPartialScan(
748                                         mInitialPartialScanChannelCount, false);
749                             }
750                             mFailedInitialPartialScan = false;
751                             mInitialPartialScanChannelCount = 0;
752                         }
753                     });
754         }
755 
756         @Override
onFullResult(ScanResult fullScanResult)757         public void onFullResult(ScanResult fullScanResult) {
758             if (!mWifiEnabled || !mAutoJoinEnabled) {
759                 return;
760             }
761 
762             if (mDbg) {
763                 localLog("AllSingleScanListener onFullResult: " + fullScanResult.SSID
764                         + " capabilities " + fullScanResult.capabilities);
765             }
766 
767             // When the scan result has radio chain info, ensure we throw away scan results
768             // not received with both radio chains (if |mUseSingleRadioChainScanResults| is false).
769             if (!mContext.getResources().getBoolean(
770                     R.bool.config_wifi_framework_use_single_radio_chain_scan_results_network_selection)
771                     && fullScanResult.radioChainInfos != null
772                     && fullScanResult.radioChainInfos.length == 1) {
773                 // Keep track of the number of dropped scan results for logging.
774                 mNumScanResultsIgnoredDueToSingleRadioChain++;
775                 return;
776             }
777 
778             mScanDetails.add(ScanResultUtil.toScanDetail(fullScanResult));
779         }
780     }
781 
782     private final AllSingleScanListener mAllSingleScanListener = new AllSingleScanListener();
783 
784     // Single scan results listener. A single scan is initiated when
785     // DisconnectedPNO scan found a valid network and woke up
786     // the system, or by the watchdog timer, or to form the timer based
787     // periodic scan.
788     //
789     // Note: This is the listener for the single scans initiated by the
790     //        WifiConnectivityManager.
791     private class SingleScanListener implements WifiScanner.ScanListener {
792         private final boolean mIsFullBandScan;
793 
SingleScanListener(boolean isFullBandScan)794         SingleScanListener(boolean isFullBandScan) {
795             mIsFullBandScan = isFullBandScan;
796         }
797 
798         @Override
onSuccess()799         public void onSuccess() {
800         }
801 
802         @Override
onFailure(int reason, String description)803         public void onFailure(int reason, String description) {
804             localLog("SingleScanListener onFailure:"
805                     + " reason: " + reason + " description: " + description);
806 
807             // reschedule the scan
808             if (mSingleScanRestartCount++ < MAX_SCAN_RESTART_ALLOWED) {
809                 scheduleDelayedSingleScan(mIsFullBandScan);
810             } else {
811                 mSingleScanRestartCount = 0;
812                 localLog("Failed to successfully start single scan for "
813                         + MAX_SCAN_RESTART_ALLOWED + " times");
814             }
815         }
816 
817         @Override
onPeriodChanged(int periodInMs)818         public void onPeriodChanged(int periodInMs) {
819             localLog("SingleScanListener onPeriodChanged: "
820                     + "actual scan period " + periodInMs + "ms");
821         }
822 
823         @Override
onResults(WifiScanner.ScanData[] results)824         public void onResults(WifiScanner.ScanData[] results) {
825             mSingleScanRestartCount = 0;
826         }
827 
828         @Override
onFullResult(ScanResult fullScanResult)829         public void onFullResult(ScanResult fullScanResult) {
830         }
831     }
832 
833     // PNO scan results listener for both disconnected and connected PNO scanning.
834     // A PNO scan is initiated when screen is off.
835     private class PnoScanListener implements WifiScanner.PnoScanListener {
836         private List<ScanDetail> mScanDetails = new ArrayList<ScanDetail>();
837         private int mLowRssiNetworkRetryDelayMs;
838 
limitLowRssiNetworkRetryDelay()839         private void limitLowRssiNetworkRetryDelay() {
840             mLowRssiNetworkRetryDelayMs = Math.min(mLowRssiNetworkRetryDelayMs,
841                     mContext.getResources().getInteger(R.integer
842                             .config_wifiPnoScanLowRssiNetworkRetryMaxDelaySec) * 1000);
843         }
844 
clearScanDetails()845         public void clearScanDetails() {
846             mScanDetails.clear();
847         }
848 
849         // Reset to the start value when either a non-PNO scan is started or
850         // WifiNetworkSelector selects a candidate from the PNO scan results.
resetLowRssiNetworkRetryDelay()851         public void resetLowRssiNetworkRetryDelay() {
852             mLowRssiNetworkRetryDelayMs = mContext.getResources().getInteger(R.integer
853                     .config_wifiPnoScanLowRssiNetworkRetryStartDelaySec) * 1000;
854         }
855 
856         @VisibleForTesting
getLowRssiNetworkRetryDelay()857         public int getLowRssiNetworkRetryDelay() {
858             return mLowRssiNetworkRetryDelayMs;
859         }
860 
861         @Override
onSuccess()862         public void onSuccess() {
863         }
864 
865         @Override
onFailure(int reason, String description)866         public void onFailure(int reason, String description) {
867             localLog("PnoScanListener onFailure:"
868                     + " reason: " + reason + " description: " + description);
869 
870             // reschedule the scan
871             if (mScanRestartCount++ < MAX_SCAN_RESTART_ALLOWED) {
872                 scheduleDelayedConnectivityScan(RESTART_SCAN_DELAY_MS);
873             } else {
874                 mScanRestartCount = 0;
875                 localLog("Failed to successfully start PNO scan for "
876                         + MAX_SCAN_RESTART_ALLOWED + " times");
877             }
878         }
879 
880         @Override
onPeriodChanged(int periodInMs)881         public void onPeriodChanged(int periodInMs) {
882             localLog("PnoScanListener onPeriodChanged: "
883                     + "actual scan period " + periodInMs + "ms");
884         }
885 
886         // Currently the PNO scan results doesn't include IE,
887         // which contains information required by WifiNetworkSelector. Ignore them
888         // for now.
889         @Override
onResults(WifiScanner.ScanData[] results)890         public void onResults(WifiScanner.ScanData[] results) {
891         }
892 
893         @Override
onFullResult(ScanResult fullScanResult)894         public void onFullResult(ScanResult fullScanResult) {
895         }
896 
897         @Override
onPnoNetworkFound(ScanResult[] results)898         public void onPnoNetworkFound(ScanResult[] results) {
899             for (ScanResult result: results) {
900                 if (result.informationElements == null) {
901                     localLog("Skipping scan result with null information elements");
902                     continue;
903                 }
904                 mScanDetails.add(ScanResultUtil.toScanDetail(result));
905             }
906 
907             // Create a new list to avoid looping call trigger concurrent exception.
908             List<ScanDetail> scanDetailList = new ArrayList<>(mScanDetails);
909             clearScanDetails();
910             mScanRestartCount = 0;
911 
912             handleScanResults(scanDetailList, PNO_SCAN_LISTENER, false,
913                     wasCandidateSelected -> {
914                         if (!wasCandidateSelected) {
915                             // The scan results were rejected by WifiNetworkSelector due to low
916                             // RSSI values
917                             // Lazy initialization
918                             if (mLowRssiNetworkRetryDelayMs == 0) {
919                                 resetLowRssiNetworkRetryDelay();
920                             }
921                             scheduleDelayedConnectivityScan(mLowRssiNetworkRetryDelayMs);
922 
923                             // Set up the delay value for next retry.
924                             mLowRssiNetworkRetryDelayMs *= 2;
925                             limitLowRssiNetworkRetryDelay();
926                         } else {
927                             resetLowRssiNetworkRetryDelay();
928                         }
929                     });
930         }
931     }
932 
933     private final PnoScanListener mPnoScanListener = new PnoScanListener();
934 
935     private class OnNetworkUpdateListener implements
936             WifiConfigManager.OnNetworkUpdateListener {
937         @Override
onNetworkAdded(WifiConfiguration config)938         public void onNetworkAdded(WifiConfiguration config) {
939             triggerScanOnNetworkChanges();
940         }
941         @Override
onNetworkEnabled(WifiConfiguration config)942         public void onNetworkEnabled(WifiConfiguration config) {
943             triggerScanOnNetworkChanges();
944         }
945         @Override
onNetworkRemoved(WifiConfiguration config)946         public void onNetworkRemoved(WifiConfiguration config) {
947             triggerScanOnNetworkChanges();
948         }
949         @Override
onNetworkUpdated(WifiConfiguration newConfig, WifiConfiguration oldConfig)950         public void onNetworkUpdated(WifiConfiguration newConfig, WifiConfiguration oldConfig) {
951             triggerScanOnNetworkChanges();
952         }
953 
954         @Override
onNetworkPermanentlyDisabled(WifiConfiguration config, int disableReason)955         public void onNetworkPermanentlyDisabled(WifiConfiguration config, int disableReason) {
956             triggerScanOnNetworkChanges();
957         }
958     }
959 
960     private class OnSuggestionUpdateListener implements
961             WifiNetworkSuggestionsManager.OnSuggestionUpdateListener {
962         @Override
onSuggestionsAddedOrUpdated(List<WifiNetworkSuggestion> suggestions)963         public void onSuggestionsAddedOrUpdated(List<WifiNetworkSuggestion> suggestions) {
964             triggerScanOnNetworkChanges();
965         }
966 
967         @Override
onSuggestionsRemoved(List<WifiNetworkSuggestion> suggestions)968         public void onSuggestionsRemoved(List<WifiNetworkSuggestion> suggestions) {
969             triggerScanOnNetworkChanges();
970         }
971     }
972 
973     private class ModeChangeCallback implements ActiveModeWarden.ModeChangeCallback {
974         @Override
onActiveModeManagerAdded(@onNull ActiveModeManager activeModeManager)975         public void onActiveModeManagerAdded(@NonNull ActiveModeManager activeModeManager) {
976             update();
977         }
978 
979         @Override
onActiveModeManagerRemoved(@onNull ActiveModeManager activeModeManager)980         public void onActiveModeManagerRemoved(@NonNull ActiveModeManager activeModeManager) {
981             update();
982         }
983 
984         @Override
onActiveModeManagerRoleChanged(@onNull ActiveModeManager activeModeManager)985         public void onActiveModeManagerRoleChanged(@NonNull ActiveModeManager activeModeManager) {
986             update();
987         }
988 
update()989         private void update() {
990             List<ClientModeManager> primaryManagers =
991                     mActiveModeWarden.getInternetConnectivityClientModeManagers();
992             setWifiEnabled(!primaryManagers.isEmpty());
993         }
994     }
995 
996     /**
997      * WifiConnectivityManager constructor
998      */
WifiConnectivityManager( WifiContext context, ScoringParams scoringParams, WifiConfigManager configManager, WifiNetworkSuggestionsManager wifiNetworkSuggestionsManager, WifiNetworkSelector networkSelector, WifiConnectivityHelper connectivityHelper, WifiLastResortWatchdog wifiLastResortWatchdog, OpenNetworkNotifier openNetworkNotifier, WifiMetrics wifiMetrics, Handler handler, Clock clock, LocalLog localLog, WifiScoreCard scoreCard, WifiBlocklistMonitor wifiBlocklistMonitor, WifiChannelUtilization wifiChannelUtilization, PasspointManager passpointManager, DeviceConfigFacade deviceConfigFacade, ActiveModeWarden activeModeWarden, WifiGlobals wifiGlobals)999     WifiConnectivityManager(
1000             WifiContext context,
1001             ScoringParams scoringParams,
1002             WifiConfigManager configManager,
1003             WifiNetworkSuggestionsManager wifiNetworkSuggestionsManager,
1004             WifiNetworkSelector networkSelector,
1005             WifiConnectivityHelper connectivityHelper,
1006             WifiLastResortWatchdog wifiLastResortWatchdog,
1007             OpenNetworkNotifier openNetworkNotifier,
1008             WifiMetrics wifiMetrics,
1009             Handler handler,
1010             Clock clock,
1011             LocalLog localLog,
1012             WifiScoreCard scoreCard,
1013             WifiBlocklistMonitor wifiBlocklistMonitor,
1014             WifiChannelUtilization wifiChannelUtilization,
1015             PasspointManager passpointManager,
1016             DeviceConfigFacade deviceConfigFacade,
1017             ActiveModeWarden activeModeWarden,
1018             WifiGlobals wifiGlobals) {
1019         mContext = context;
1020         mScoringParams = scoringParams;
1021         mConfigManager = configManager;
1022         mWifiNetworkSuggestionsManager = wifiNetworkSuggestionsManager;
1023         mNetworkSelector = networkSelector;
1024         mConnectivityHelper = connectivityHelper;
1025         mWifiLastResortWatchdog = wifiLastResortWatchdog;
1026         mOpenNetworkNotifier = openNetworkNotifier;
1027         mWifiMetrics = wifiMetrics;
1028         mEventHandler = handler;
1029         mClock = clock;
1030         mLocalLog = localLog;
1031         mWifiScoreCard = scoreCard;
1032         mWifiBlocklistMonitor = wifiBlocklistMonitor;
1033         mWifiChannelUtilization = wifiChannelUtilization;
1034         mPasspointManager = passpointManager;
1035         mDeviceConfigFacade = deviceConfigFacade;
1036         mActiveModeWarden = activeModeWarden;
1037         mWifiGlobals = wifiGlobals;
1038 
1039         mAlarmManager = context.getSystemService(AlarmManager.class);
1040         mPowerManager = mContext.getSystemService(PowerManager.class);
1041 
1042         // Listen for screen state change events.
1043         // TODO: We should probably add a shared broadcast receiver in the wifi stack which
1044         // can used by various modules to listen to common system events. Creating multiple
1045         // broadcast receivers in each class within the wifi stack is *somewhat* expensive.
1046         IntentFilter filter = new IntentFilter();
1047         filter.addAction(Intent.ACTION_SCREEN_ON);
1048         filter.addAction(Intent.ACTION_SCREEN_OFF);
1049         mContext.registerReceiver(
1050                 new BroadcastReceiver() {
1051                     @Override
1052                     public void onReceive(Context context, Intent intent) {
1053                         String action = intent.getAction();
1054                         if (action.equals(Intent.ACTION_SCREEN_ON)) {
1055                             handleScreenStateChanged(true);
1056                         } else if (action.equals(Intent.ACTION_SCREEN_OFF)) {
1057                             handleScreenStateChanged(false);
1058                         }
1059                     }
1060                 }, filter, null, mEventHandler);
1061         handleScreenStateChanged(mPowerManager.isInteractive());
1062 
1063         // Listen to WifiConfigManager network update events
1064         mConfigManager.addOnNetworkUpdateListener(new OnNetworkUpdateListener());
1065         // Listen to WifiNetworkSuggestionsManager suggestion update events
1066         mWifiNetworkSuggestionsManager.addOnSuggestionUpdateListener(
1067                 new OnSuggestionUpdateListener());
1068         mActiveModeWarden.registerModeChangeCallback(new ModeChangeCallback());
1069     }
1070 
1071     @NonNull
getPrimaryWifiInfo()1072     private WifiInfo getPrimaryWifiInfo() {
1073         return getPrimaryClientModeManager().syncRequestConnectionInfo();
1074     }
1075 
getPrimaryClientModeManager()1076     private ClientModeManager getPrimaryClientModeManager() {
1077         // There should only be 1 primary client mode manager at any point of time.
1078         return mActiveModeWarden.getPrimaryClientModeManager();
1079     }
1080 
1081     /** Initialize single scanning schedules, and validate them */
initializeScanningSchedule(int state)1082     private int[] initializeScanningSchedule(int state) {
1083         int[] scheduleSec;
1084 
1085         if (state == WIFI_STATE_CONNECTED) {
1086             scheduleSec = mContext.getResources().getIntArray(
1087                     R.array.config_wifiConnectedScanIntervalScheduleSec);
1088         } else if (state == WIFI_STATE_DISCONNECTED) {
1089             scheduleSec = mContext.getResources().getIntArray(
1090                     R.array.config_wifiDisconnectedScanIntervalScheduleSec);
1091         } else {
1092             scheduleSec = null;
1093         }
1094 
1095         boolean invalidConfig = false;
1096         if (scheduleSec == null || scheduleSec.length == 0) {
1097             invalidConfig = true;
1098         } else {
1099             for (int val : scheduleSec) {
1100                 if (val <= 0) {
1101                     invalidConfig = true;
1102                     break;
1103                 }
1104             }
1105         }
1106         if (!invalidConfig) {
1107             return scheduleSec;
1108         }
1109 
1110         Log.e(TAG, "Configuration for wifi scanning schedule is mis-configured,"
1111                 + "using default schedule");
1112         return DEFAULT_SCANNING_SCHEDULE_SEC;
1113     }
1114 
1115     /**
1116      * This checks the connection attempt rate and recommends whether the connection attempt
1117      * should be skipped or not. This attempts to rate limit the rate of connections to
1118      * prevent us from flapping between networks and draining battery rapidly.
1119      */
shouldSkipConnectionAttempt(long timeMillis)1120     private boolean shouldSkipConnectionAttempt(long timeMillis) {
1121         Iterator<Long> attemptIter = mConnectionAttemptTimeStamps.iterator();
1122         // First evict old entries from the queue.
1123         while (attemptIter.hasNext()) {
1124             Long connectionAttemptTimeMillis = attemptIter.next();
1125             if ((timeMillis - connectionAttemptTimeMillis)
1126                     > MAX_CONNECTION_ATTEMPTS_TIME_INTERVAL_MS) {
1127                 attemptIter.remove();
1128             } else {
1129                 // This list is sorted by timestamps, so we can skip any more checks
1130                 break;
1131             }
1132         }
1133         // If we've reached the max connection attempt rate, skip this connection attempt
1134         return (mConnectionAttemptTimeStamps.size() >= MAX_CONNECTION_ATTEMPTS_RATE);
1135     }
1136 
1137     /**
1138      * Add the current connection attempt timestamp to our queue of connection attempts.
1139      */
noteConnectionAttempt(long timeMillis)1140     private void noteConnectionAttempt(long timeMillis) {
1141         localLog("noteConnectionAttempt: timeMillis=" + timeMillis);
1142         mConnectionAttemptTimeStamps.addLast(timeMillis);
1143     }
1144 
1145     /**
1146      * This is used to clear the connection attempt rate limiter. This is done when the user
1147      * explicitly tries to connect to a specified network.
1148      */
clearConnectionAttemptTimeStamps()1149     private void clearConnectionAttemptTimeStamps() {
1150         mConnectionAttemptTimeStamps.clear();
1151     }
1152 
coalesce(T a, T b)1153     private static <T> T coalesce(T a, T  b) {
1154         return a != null ? a : b;
1155     }
1156 
isClientModeManagerConnectedOrConnectingToCandidate( ClientModeManager clientModeManager, WifiConfiguration candidate)1157     private boolean isClientModeManagerConnectedOrConnectingToCandidate(
1158             ClientModeManager clientModeManager, WifiConfiguration candidate) {
1159         int targetNetworkId = candidate.networkId;
1160         WifiConfiguration connectedOrConnectingWifiConfiguration = coalesce(
1161                 clientModeManager.getConnectingWifiConfiguration(),
1162                 clientModeManager.getConnectedWifiConfiguration());
1163         boolean connectingOrConnectedToTarget =
1164                 connectedOrConnectingWifiConfiguration != null
1165                         && (targetNetworkId == connectedOrConnectingWifiConfiguration.networkId
1166                         || (mContext.getResources().getBoolean(
1167                                 R.bool.config_wifiEnableLinkedNetworkRoaming)
1168                         && connectedOrConnectingWifiConfiguration.isLinked(candidate)));
1169 
1170         // Is Firmware roaming control is supported?
1171         //   - Yes, framework does nothing, firmware will roam if necessary.
1172         //   - No, framework initiates roaming.
1173         if (mConnectivityHelper.isFirmwareRoamingSupported()) {
1174             // just check for networkID.
1175             return connectingOrConnectedToTarget;
1176         }
1177 
1178         // check for networkID and BSSID.
1179         String connectedOrConnectingBssid = coalesce(
1180                 clientModeManager.getConnectingBssid(),
1181                 clientModeManager.getConnectedBssid());
1182         ScanResult scanResultCandidate =
1183                 candidate.getNetworkSelectionStatus().getCandidate();
1184         String targetBssid = scanResultCandidate.BSSID;
1185         return connectingOrConnectedToTarget
1186                 && Objects.equals(targetBssid, connectedOrConnectingBssid);
1187     }
1188 
1189     /**
1190      * Trigger network connection for primary client mode manager using make before break.
1191      *
1192      * Note: This may trigger make before break on a secondary STA if available which will
1193      * eventually become primary after validation or torn down if it does not become primary.
1194      */
connectToNetworkForPrimaryCmmUsingMbbIfAvailable( @onNull WifiConfiguration candidate)1195     private void connectToNetworkForPrimaryCmmUsingMbbIfAvailable(
1196             @NonNull WifiConfiguration candidate) {
1197         ClientModeManager primaryManager = mActiveModeWarden.getPrimaryClientModeManager();
1198         connectToNetworkUsingCmm(
1199                 primaryManager, candidate,
1200                 new ConnectHandler() {
1201                     @Override
1202                     public void triggerConnectWhenDisconnected(
1203                             WifiConfiguration targetNetwork,
1204                             String targetBssid) {
1205                         triggerConnectToNetworkUsingCmm(primaryManager, targetNetwork, targetBssid);
1206                         // since using primary manager to connect, stop any existing managers in the
1207                         // secondary transient role since they are no longer needed.
1208                         mActiveModeWarden.stopAllClientModeManagersInRole(
1209                                 ROLE_CLIENT_SECONDARY_TRANSIENT);
1210                     }
1211 
1212                     @Override
1213                     public void triggerConnectWhenConnected(
1214                             WifiConfiguration currentNetwork,
1215                             WifiConfiguration targetNetwork,
1216                             String targetBssid) {
1217                         mWifiMetrics.incrementWifiToWifiSwitchTriggerCount();
1218                         // If both the current & target networks have MAC randomization disabled,
1219                         // we cannot use MBB because then both ifaces would need to use the exact
1220                         // same MAC address (the "designated" factory MAC for the device), which is
1221                         // illegal. Fallback to single STA behavior.
1222 
1223                         // TODO(b/172086124): Possibly move this logic to
1224                         // ActiveModeWarden.handleAdditionalClientModeManagerRequest() to
1225                         // ensure that all fallback logic in 1 central place (all the necessary
1226                         // info is already included in the secondary STA creation request).
1227                         if (currentNetwork.macRandomizationSetting == RANDOMIZATION_NONE
1228                                 && targetNetwork.macRandomizationSetting == RANDOMIZATION_NONE) {
1229                             triggerConnectToNetworkUsingCmm(
1230                                     primaryManager, targetNetwork, targetBssid);
1231                             // since using primary manager to connect, stop any existing managers in
1232                             // the secondary transient role since they are no longer needed.
1233                             mActiveModeWarden.stopAllClientModeManagersInRole(
1234                                     ROLE_CLIENT_SECONDARY_TRANSIENT);
1235                             return;
1236                         }
1237                         // Else, use MBB if available.
1238                         triggerConnectToNetworkUsingMbbIfAvailable(targetNetwork, targetBssid);
1239                     }
1240 
1241                     @Override
1242                     public void triggerRoamWhenConnected(
1243                             WifiConfiguration currentNetwork,
1244                             WifiConfiguration targetNetwork,
1245                             String targetBssid) {
1246                         triggerRoamToNetworkUsingCmm(
1247                                 primaryManager, targetNetwork, targetBssid);
1248                         // since using primary manager to connect, stop any existing managers in the
1249                         // secondary transient role since they are no longer needed.
1250                         mActiveModeWarden.stopAllClientModeManagersInRole(
1251                                 ROLE_CLIENT_SECONDARY_TRANSIENT);
1252                     }
1253                 });
1254 
1255     }
1256 
1257     /**
1258      * Trigger network connection for provided client mode manager without using make before break.
1259      */
connectToNetworkUsingCmmWithoutMbb( @onNull ClientModeManager clientModeManager, @NonNull WifiConfiguration candidate)1260     private void connectToNetworkUsingCmmWithoutMbb(
1261             @NonNull ClientModeManager clientModeManager, @NonNull WifiConfiguration candidate) {
1262         connectToNetworkUsingCmm(clientModeManager, candidate,
1263                 new ConnectHandler() {
1264                     @Override
1265                     public void triggerConnectWhenDisconnected(
1266                             WifiConfiguration targetNetwork,
1267                             String targetBssid) {
1268                         triggerConnectToNetworkUsingCmm(
1269                                 clientModeManager, targetNetwork, targetBssid);
1270                     }
1271 
1272                     @Override
1273                     public void triggerConnectWhenConnected(
1274                             WifiConfiguration currentNetwork,
1275                             WifiConfiguration targetNetwork,
1276                             String targetBssid) {
1277                         triggerConnectToNetworkUsingCmm(
1278                                 clientModeManager, targetNetwork, targetBssid);
1279                     }
1280 
1281                     @Override
1282                     public void triggerRoamWhenConnected(
1283                             WifiConfiguration currentNetwork,
1284                             WifiConfiguration targetNetwork,
1285                             String targetBssid) {
1286                         triggerRoamToNetworkUsingCmm(
1287                                 clientModeManager, targetNetwork, targetBssid);
1288                     }
1289                 });
1290     }
1291 
1292     /**
1293      * Interface to use for trigger connection in various scenarios.
1294      */
1295     private interface ConnectHandler {
1296         /**
1297          * Invoked to trigger connection to a network when disconnected.
1298          */
triggerConnectWhenDisconnected( @onNull WifiConfiguration targetNetwork, @NonNull String targetBssid)1299         void triggerConnectWhenDisconnected(
1300                 @NonNull WifiConfiguration targetNetwork, @NonNull String targetBssid);
1301         /**
1302          * Invoked to trigger connection to a network when connected to a different network.
1303          */
triggerConnectWhenConnected( @onNull WifiConfiguration currentNetwork, @NonNull WifiConfiguration targetNetwork, @NonNull String targetBssid)1304         void triggerConnectWhenConnected(
1305                 @NonNull WifiConfiguration currentNetwork, @NonNull WifiConfiguration targetNetwork,
1306                 @NonNull String targetBssid);
1307         /**
1308          * Invoked to trigger roam to a specific bssid network when connected to a network.
1309          */
triggerRoamWhenConnected( @onNull WifiConfiguration currentNetwork, @NonNull WifiConfiguration targetNetwork, @NonNull String targetBssid)1310         void triggerRoamWhenConnected(
1311                 @NonNull WifiConfiguration currentNetwork, @NonNull WifiConfiguration targetNetwork,
1312                 @NonNull String targetBssid);
1313     }
1314 
getAssociationId(@ullable WifiConfiguration config, @Nullable String bssid)1315     private String getAssociationId(@Nullable WifiConfiguration config, @Nullable String bssid) {
1316         return config == null ? "Disconnected" : config.SSID + " : " + bssid;
1317     }
1318 
1319     /**
1320      * Attempt to connect to a network candidate.
1321      *
1322      * Based on the currently connected network, this method determines whether we should
1323      * connect or roam to the network candidate recommended by WifiNetworkSelector.
1324      */
connectToNetworkUsingCmm(@onNull ClientModeManager clientModeManager, @NonNull WifiConfiguration targetNetwork, @NonNull ConnectHandler connectHandler)1325     private void connectToNetworkUsingCmm(@NonNull ClientModeManager clientModeManager,
1326             @NonNull WifiConfiguration targetNetwork,
1327             @NonNull ConnectHandler connectHandler) {
1328         if (targetNetwork.getNetworkSelectionStatus().getCandidate() == null) {
1329             localLog("connectToNetwork(" + clientModeManager + "): bad candidate - "
1330                     + targetNetwork + " scanResult is null!");
1331             return;
1332         }
1333         String targetBssid = targetNetwork.getNetworkSelectionStatus().getCandidate().BSSID;
1334         String targetAssociationId = getAssociationId(targetNetwork, targetBssid);
1335 
1336         if (isClientModeManagerConnectedOrConnectingToCandidate(clientModeManager, targetNetwork)) {
1337             localLog("connectToNetwork(" + clientModeManager + "): either already connected or is "
1338                     + "connecting to " + targetAssociationId);
1339             return;
1340         }
1341 
1342         if (targetNetwork.BSSID != null
1343                 && !targetNetwork.BSSID.equals(ClientModeImpl.SUPPLICANT_BSSID_ANY)
1344                 && !targetNetwork.BSSID.equals(targetBssid)) {
1345             localLog("connectToNetwork(" + clientModeManager + "): target BSSID " + targetBssid
1346                     + " does not match the config specified BSSID " + targetNetwork.BSSID
1347                     + ". Drop it!");
1348             return;
1349         }
1350 
1351         WifiConfiguration currentNetwork = coalesce(
1352                 clientModeManager.getConnectedWifiConfiguration(),
1353                 clientModeManager.getConnectingWifiConfiguration());
1354         String currentBssid = coalesce(
1355                 clientModeManager.getConnectedBssid(), clientModeManager.getConnectingBssid());
1356         String currentAssociationId = getAssociationId(currentNetwork, currentBssid);
1357 
1358         // Already on desired network id, we need to trigger roam since the device does not
1359         // support firmware roaming (already checked in
1360         // isClientModeManagerConnectedOrConnectingToCandidate()).
1361         if (currentNetwork != null
1362                 && (currentNetwork.networkId == targetNetwork.networkId
1363                 || (mContext.getResources().getBoolean(R.bool.config_wifiEnableLinkedNetworkRoaming)
1364                 && currentNetwork.isLinked(targetNetwork)))) {
1365             localLog("connectToNetwork(" + clientModeManager + "): Roam to " + targetAssociationId
1366                     + " from " + currentAssociationId);
1367             connectHandler.triggerRoamWhenConnected(currentNetwork, targetNetwork, targetBssid);
1368             return;
1369         }
1370 
1371         // Need to connect to a different network id
1372         // Framework specifies the connection target BSSID if firmware doesn't support
1373         // {@link android.net.wifi.WifiManager#WIFI_FEATURE_CONTROL_ROAMING} or the
1374         // candidate configuration contains a specified BSSID.
1375         if (mConnectivityHelper.isFirmwareRoamingSupported()
1376                 && (targetNetwork.BSSID == null
1377                 || targetNetwork.BSSID.equals(ClientModeImpl.SUPPLICANT_BSSID_ANY))) {
1378             targetBssid = ClientModeImpl.SUPPLICANT_BSSID_ANY;
1379         }
1380         localLog("connectToNetwork(" + clientModeManager + "): Connect to "
1381                 + getAssociationId(targetNetwork, targetBssid) + " from "
1382                 + currentAssociationId);
1383         if (currentNetwork == null) {
1384             connectHandler.triggerConnectWhenDisconnected(targetNetwork, targetBssid);
1385             return;
1386         }
1387         connectHandler.triggerConnectWhenConnected(currentNetwork, targetNetwork, targetBssid);
1388     }
1389 
shouldConnect()1390     private boolean shouldConnect() {
1391         long elapsedTimeMillis = mClock.getElapsedSinceBootMillis();
1392         if (!mScreenOn && shouldSkipConnectionAttempt(elapsedTimeMillis)) {
1393             localLog("connectToNetwork: Too many connection attempts. Skipping this attempt!");
1394             mTotalConnectivityAttemptsRateLimited++;
1395             return false;
1396         }
1397         noteConnectionAttempt(elapsedTimeMillis);
1398         return true;
1399     }
1400 
1401     /**
1402      * Trigger roaming to a new bssid while being connected to a different bssid in same network.
1403      */
triggerRoamToNetworkUsingCmm( @onNull ClientModeManager clientModeManager, @NonNull WifiConfiguration targetNetwork, @NonNull String targetBssid)1404     private void triggerRoamToNetworkUsingCmm(
1405             @NonNull ClientModeManager clientModeManager,
1406             @NonNull WifiConfiguration targetNetwork,
1407             @NonNull String targetBssid) {
1408         if (!shouldConnect()) {
1409             return;
1410         }
1411         clientModeManager.startRoamToNetwork(targetNetwork.networkId, targetBssid);
1412     }
1413 
1414     /**
1415      * Trigger connection to a new wifi network while being disconnected.
1416      */
triggerConnectToNetworkUsingCmm( @onNull ClientModeManager clientModeManager, @NonNull WifiConfiguration targetNetwork, @NonNull String targetBssid)1417     private void triggerConnectToNetworkUsingCmm(
1418             @NonNull ClientModeManager clientModeManager,
1419             @NonNull WifiConfiguration targetNetwork, @NonNull String targetBssid) {
1420         if (!shouldConnect()) {
1421             return;
1422         }
1423         clientModeManager.startConnectToNetwork(
1424                 targetNetwork.networkId, Process.WIFI_UID, targetBssid);
1425     }
1426 
1427     /**
1428      * Trigger connection to a new wifi network while being connected to another network.
1429      * Depending on device configuration, this uses
1430      *  - MBB make before break (Dual STA), or
1431      *  - BBM break before make (Single STA)
1432      */
triggerConnectToNetworkUsingMbbIfAvailable( @onNull WifiConfiguration targetNetwork, @NonNull String targetBssid)1433     private void triggerConnectToNetworkUsingMbbIfAvailable(
1434             @NonNull WifiConfiguration targetNetwork, @NonNull String targetBssid) {
1435         // Request a ClientModeManager from ActiveModeWarden to connect with - may be an existing
1436         // CMM or a newly created one (potentially switching networks using Make-Before-Break)
1437         mActiveModeWarden.requestSecondaryTransientClientModeManager(
1438                 (@Nullable ClientModeManager clientModeManager) -> {
1439                     localLog("connectToNetwork: received requested ClientModeManager "
1440                             + clientModeManager);
1441                     if (clientModeManager == null) {
1442                         localLog("connectToNetwork: Wifi has been toggled off, aborting");
1443                         return;
1444                     }
1445                     // we don't know which ClientModeManager will be allocated to us. Thus, double
1446                     // check if we're already connected before connecting.
1447                     if (isClientModeManagerConnectedOrConnectingToCandidate(
1448                             clientModeManager, targetNetwork)) {
1449                         localLog("connectToNetwork: already connected or connecting to candidate="
1450                                 + targetNetwork + " on " + clientModeManager);
1451                         return;
1452                     }
1453                     if (clientModeManager.getRole() == ROLE_CLIENT_SECONDARY_TRANSIENT) {
1454                         mWifiMetrics.incrementMakeBeforeBreakTriggerCount();
1455                     }
1456                     triggerConnectToNetworkUsingCmm(clientModeManager, targetNetwork, targetBssid);
1457                 },
1458                 ActiveModeWarden.INTERNAL_REQUESTOR_WS,
1459                 targetNetwork.SSID,
1460                 mConnectivityHelper.isFirmwareRoamingSupported() ? null : targetBssid);
1461     }
1462 
1463     // Helper for selecting the band for connectivity scan
getScanBand()1464     private int getScanBand() {
1465         return getScanBand(true);
1466     }
1467 
getScanBand(boolean isFullBandScan)1468     private int getScanBand(boolean isFullBandScan) {
1469         if (isFullBandScan) {
1470             if (SdkLevel.isAtLeastS()) {
1471                 if (mContext.getResources().getBoolean(R.bool.config_wifiEnable6ghzPscScanning)) {
1472                     return WifiScanner.WIFI_BAND_24_5_WITH_DFS_6_GHZ;
1473                 }
1474                 return WifiScanner.WIFI_BAND_BOTH_WITH_DFS;
1475             }
1476             return WifiScanner.WIFI_BAND_ALL;
1477         } else {
1478             // Use channel list instead.
1479             return WifiScanner.WIFI_BAND_UNSPECIFIED;
1480         }
1481     }
1482 
1483     // Helper for setting the channels for connectivity scan when band is unspecified. Returns
1484     // false if we can't retrieve the info.
1485     // If connected, return channels used for the connected network
1486     // If disconnected, return channels used for any network.
setScanChannels(ScanSettings settings)1487     private boolean setScanChannels(ScanSettings settings) {
1488         Set<Integer> freqs;
1489 
1490         WifiConfiguration config = getPrimaryClientModeManager().getConnectedWifiConfiguration();
1491         if (config == null) {
1492             long ageInMillis = 1000 * 60 * mContext.getResources().getInteger(
1493                     R.integer.config_wifiInitialPartialScanChannelCacheAgeMins);
1494             int maxCount = mContext.getResources().getInteger(
1495                     R.integer.config_wifiInitialPartialScanChannelMaxCount);
1496             freqs = fetchChannelSetForPartialScan(maxCount, ageInMillis);
1497         } else {
1498             freqs = fetchChannelSetForNetworkForPartialScan(config.networkId);
1499         }
1500 
1501         if (freqs != null && freqs.size() != 0) {
1502             int index = 0;
1503             settings.channels = new WifiScanner.ChannelSpec[freqs.size()];
1504             for (Integer freq : freqs) {
1505                 settings.channels[index++] = new WifiScanner.ChannelSpec(freq);
1506             }
1507             return true;
1508         } else {
1509             localLog("No history scan channels found, Perform full band scan");
1510             return false;
1511         }
1512     }
1513 
1514     /**
1515      * Add the channels into the channel set with a size limit.
1516      * If maxCount equals to 0, will add all available channels into the set.
1517      * @param channelSet Target set for adding channel to.
1518      * @param config Network for query channel from WifiScoreCard
1519      * @param maxCount Size limit of the set. If equals to 0, means no limit.
1520      * @param ageInMillis Only consider channel info whose timestamps are younger than this value.
1521      * @return True if all available channels for this network are added, otherwise false.
1522      */
addChannelFromWifiScoreCard(@onNull Set<Integer> channelSet, @NonNull WifiConfiguration config, int maxCount, long ageInMillis)1523     private boolean addChannelFromWifiScoreCard(@NonNull Set<Integer> channelSet,
1524             @NonNull WifiConfiguration config, int maxCount, long ageInMillis) {
1525         WifiScoreCard.PerNetwork network = mWifiScoreCard.lookupNetwork(config.SSID);
1526         for (Integer channel : network.getFrequencies(ageInMillis)) {
1527             if (maxCount > 0 && channelSet.size() >= maxCount) {
1528                 localLog("addChannelFromWifiScoreCard: size limit reached for network:"
1529                         + config.SSID);
1530                 return false;
1531             }
1532             channelSet.add(channel);
1533         }
1534         return true;
1535     }
1536 
1537     /**
1538      * Fetch channel set for target network.
1539      */
1540     @VisibleForTesting
fetchChannelSetForNetworkForPartialScan(int networkId)1541     public Set<Integer> fetchChannelSetForNetworkForPartialScan(int networkId) {
1542         WifiConfiguration config = mConfigManager.getConfiguredNetwork(networkId);
1543         if (config == null) {
1544             return null;
1545         }
1546         final int maxNumActiveChannelsForPartialScans = mContext.getResources().getInteger(
1547                 R.integer.config_wifi_framework_associated_partial_scan_max_num_active_channels);
1548         Set<Integer> channelSet = new HashSet<>();
1549         WifiInfo wifiInfo = getPrimaryWifiInfo();
1550         // First add the currently connected network channel.
1551         if (wifiInfo.getFrequency() > 0) {
1552             channelSet.add(wifiInfo.getFrequency());
1553         }
1554         // Then get channels for the network.
1555         addChannelFromWifiScoreCard(channelSet, config, maxNumActiveChannelsForPartialScans,
1556                 CHANNEL_LIST_AGE_MS);
1557         return channelSet;
1558     }
1559 
1560     /**
1561      * Fetch channel set for all saved and suggestion non-passpoint network for partial scan.
1562      */
1563     @VisibleForTesting
fetchChannelSetForPartialScan(int maxCount, long ageInMillis)1564     public Set<Integer> fetchChannelSetForPartialScan(int maxCount, long ageInMillis) {
1565         List<WifiConfiguration> networks = getAllScanOptimizationNetworks();
1566         if (networks.isEmpty()) {
1567             return null;
1568         }
1569 
1570         // Sort the networks with the most frequent ones at the front of the network list.
1571         Collections.sort(networks, mConfigManager.getScanListComparator());
1572 
1573         Set<Integer> channelSet = new HashSet<>();
1574 
1575         for (WifiConfiguration config : networks) {
1576             if (!addChannelFromWifiScoreCard(channelSet, config, maxCount, ageInMillis)) {
1577                 return channelSet;
1578             }
1579         }
1580 
1581         return channelSet;
1582     }
1583 
1584     // Watchdog timer handler
watchdogHandler()1585     private void watchdogHandler() {
1586         // Schedule the next timer and start a single scan if we are in disconnected state.
1587         // Otherwise, the watchdog timer will be scheduled when entering disconnected
1588         // state.
1589         if (mWifiState == WIFI_STATE_DISCONNECTED) {
1590             localLog("start a single scan from watchdogHandler");
1591 
1592             scheduleWatchdogTimer();
1593             startSingleScan(true, WIFI_WORK_SOURCE);
1594         }
1595     }
1596 
triggerScanOnNetworkChanges()1597     private void triggerScanOnNetworkChanges() {
1598         if (mScreenOn) {
1599             // Update scanning schedule if needed
1600             if (updateSingleScanningSchedule()) {
1601                 localLog("Saved networks / suggestions updated impacting single scan schedule");
1602                 startConnectivityScan(false);
1603             }
1604         } else {
1605             // Update the PNO scan network list when screen is off. Here we
1606             // rely on startConnectivityScan() to perform all the checks and clean up.
1607             localLog("Saved networks / suggestions updated impacting pno scan");
1608             startConnectivityScan(false);
1609         }
1610     }
1611 
1612     // Start a single scan and set up the interval for next single scan.
startPeriodicSingleScan()1613     private void startPeriodicSingleScan() {
1614         // Reaching here with scanning schedule is null means this is a false timer alarm
1615         if (getSingleScanningSchedule() == null) {
1616             return;
1617         }
1618 
1619         long currentTimeStamp = mClock.getElapsedSinceBootMillis();
1620 
1621         if (mLastPeriodicSingleScanTimeStamp != RESET_TIME_STAMP) {
1622             long msSinceLastScan = currentTimeStamp - mLastPeriodicSingleScanTimeStamp;
1623             if (msSinceLastScan < getScheduledSingleScanIntervalMs(0)) {
1624                 localLog("Last periodic single scan started " + msSinceLastScan
1625                         + "ms ago, defer this new scan request.");
1626                 schedulePeriodicScanTimer(
1627                         getScheduledSingleScanIntervalMs(0) - (int) msSinceLastScan);
1628                 return;
1629             }
1630         }
1631 
1632         boolean isScanNeeded = true;
1633         boolean isFullBandScan = true;
1634 
1635         boolean isShortTimeSinceLastNetworkSelection =
1636                 ((currentTimeStamp - mLastNetworkSelectionTimeStamp)
1637                 <= 1000 * mContext.getResources().getInteger(
1638                 R.integer.config_wifiConnectedHighRssiScanMinimumWindowSizeSec));
1639 
1640         WifiInfo wifiInfo = getPrimaryWifiInfo();
1641         boolean isGoodLinkAndAcceptableInternetAndShortTimeSinceLastNetworkSelection =
1642                 mNetworkSelector.hasSufficientLinkQuality(wifiInfo)
1643                 && mNetworkSelector.hasInternetOrExpectNoInternet(wifiInfo)
1644                 && isShortTimeSinceLastNetworkSelection;
1645         // Check it is one of following conditions to skip scan (with firmware roaming)
1646         // or do partial scan only (without firmware roaming).
1647         // 1) Network is sufficient
1648         // 2) link is good, internet status is acceptable
1649         //    and it is a short time since last network selection
1650         // 3) There is active stream such that scan will be likely disruptive
1651         if (mWifiState == WIFI_STATE_CONNECTED
1652                 && (mNetworkSelector.isNetworkSufficient(wifiInfo)
1653                 || isGoodLinkAndAcceptableInternetAndShortTimeSinceLastNetworkSelection
1654                 || mNetworkSelector.hasActiveStream(wifiInfo))) {
1655             // If only partial scan is proposed and firmware roaming control is supported,
1656             // we will not issue any scan because firmware roaming will take care of
1657             // intra-SSID roam.
1658             if (mConnectivityHelper.isFirmwareRoamingSupported()) {
1659                 localLog("No partial scan because firmware roaming is supported.");
1660                 isScanNeeded = false;
1661             } else {
1662                 localLog("No full band scan because current network is sufficient");
1663                 isFullBandScan = false;
1664             }
1665         }
1666 
1667         if (isScanNeeded) {
1668             mLastPeriodicSingleScanTimeStamp = currentTimeStamp;
1669 
1670             if (mWifiState == WIFI_STATE_DISCONNECTED
1671                     && mInitialScanState == INITIAL_SCAN_STATE_START) {
1672                 startSingleScan(false, WIFI_WORK_SOURCE);
1673 
1674                 // Note, initial partial scan may fail due to lack of channel history
1675                 // Hence, we verify state before changing to AWIATING_RESPONSE
1676                 if (mInitialScanState == INITIAL_SCAN_STATE_START) {
1677                     setInitialScanState(INITIAL_SCAN_STATE_AWAITING_RESPONSE);
1678                     mWifiMetrics.incrementInitialPartialScanCount();
1679                 }
1680             } else {
1681                 startSingleScan(isFullBandScan, WIFI_WORK_SOURCE);
1682             }
1683             schedulePeriodicScanTimer(
1684                     getScheduledSingleScanIntervalMs(mCurrentSingleScanScheduleIndex));
1685 
1686             // Set up the next scan interval in an exponential backoff fashion.
1687             mCurrentSingleScanScheduleIndex++;
1688         } else {
1689             // Since we already skipped this scan, keep the same scan interval for next scan.
1690             schedulePeriodicScanTimer(
1691                     getScheduledSingleScanIntervalMs(mCurrentSingleScanScheduleIndex));
1692         }
1693     }
1694 
1695     // Retrieve a value from single scanning schedule in ms
getScheduledSingleScanIntervalMs(int index)1696     private int getScheduledSingleScanIntervalMs(int index) {
1697         synchronized (mLock) {
1698             if (mCurrentSingleScanScheduleSec == null) {
1699                 Log.e(TAG, "Invalid attempt to get schedule interval, Schedule array is null ");
1700 
1701                 // Use a default value
1702                 return DEFAULT_SCANNING_SCHEDULE_SEC[0] * 1000;
1703             }
1704 
1705             if (index >= mCurrentSingleScanScheduleSec.length) {
1706                 index = mCurrentSingleScanScheduleSec.length - 1;
1707             }
1708             return getScanIntervalWithPowerSaveMultiplier(
1709                     mCurrentSingleScanScheduleSec[index] * 1000);
1710         }
1711     }
1712 
getScanIntervalWithPowerSaveMultiplier(int interval)1713     private int getScanIntervalWithPowerSaveMultiplier(int interval) {
1714         if (!mDeviceConfigFacade.isWifiBatterySaverEnabled()) {
1715             return interval;
1716         }
1717         return mPowerManager.isPowerSaveMode()
1718                 ? POWER_SAVE_SCAN_INTERVAL_MULTIPLIER * interval : interval;
1719     }
1720 
1721     // Set the single scanning schedule
setSingleScanningSchedule(int[] scheduleSec)1722     private void setSingleScanningSchedule(int[] scheduleSec) {
1723         synchronized (mLock) {
1724             mCurrentSingleScanScheduleSec = scheduleSec;
1725         }
1726     }
1727 
1728     // Get the single scanning schedule
getSingleScanningSchedule()1729     private int[] getSingleScanningSchedule() {
1730         synchronized (mLock) {
1731             return mCurrentSingleScanScheduleSec;
1732         }
1733     }
1734 
1735     // Update the single scanning schedule if needed, and return true if update occurs
updateSingleScanningSchedule()1736     private boolean updateSingleScanningSchedule() {
1737         if (!mWifiEnabled || !mAutoJoinEnabled) {
1738             return false;
1739         }
1740         if (mWifiState != WIFI_STATE_CONNECTED) {
1741             // No need to update the scanning schedule
1742             return false;
1743         }
1744 
1745         boolean shouldUseSingleSavedNetworkSchedule = useSingleSavedNetworkSchedule();
1746 
1747         if (mCurrentSingleScanScheduleSec == mConnectedSingleScanScheduleSec
1748                 && shouldUseSingleSavedNetworkSchedule) {
1749             mCurrentSingleScanScheduleSec = mConnectedSingleSavedNetworkSingleScanScheduleSec;
1750             return true;
1751         }
1752         if (mCurrentSingleScanScheduleSec == mConnectedSingleSavedNetworkSingleScanScheduleSec
1753                 && !shouldUseSingleSavedNetworkSchedule) {
1754             mCurrentSingleScanScheduleSec = mConnectedSingleScanScheduleSec;
1755             return true;
1756         }
1757         return false;
1758     }
1759 
1760     // Set initial scan state
setInitialScanState(int state)1761     private void setInitialScanState(int state) {
1762         Log.i(TAG, "SetInitialScanState to : " + state);
1763         mInitialScanState = state;
1764     }
1765 
1766     @VisibleForTesting
getInitialScanState()1767     public int getInitialScanState() {
1768         return mInitialScanState;
1769     }
1770 
1771     // Reset the last periodic single scan time stamp so that the next periodic single
1772     // scan can start immediately.
resetLastPeriodicSingleScanTimeStamp()1773     private void resetLastPeriodicSingleScanTimeStamp() {
1774         mLastPeriodicSingleScanTimeStamp = RESET_TIME_STAMP;
1775     }
1776 
1777     // Periodic scan timer handler
periodicScanTimerHandler()1778     private void periodicScanTimerHandler() {
1779         localLog("periodicScanTimerHandler");
1780 
1781         // Schedule the next timer and start a single scan if screen is on.
1782         if (mScreenOn) {
1783             startPeriodicSingleScan();
1784         }
1785     }
1786 
1787     // Start a single scan
startForcedSingleScan(boolean isFullBandScan, WorkSource workSource)1788     private void startForcedSingleScan(boolean isFullBandScan, WorkSource workSource) {
1789         mPnoScanListener.resetLowRssiNetworkRetryDelay();
1790 
1791         ScanSettings settings = new ScanSettings();
1792         if (!isFullBandScan) {
1793             if (!setScanChannels(settings)) {
1794                 isFullBandScan = true;
1795                 // Skip the initial scan since no channel history available
1796                 setInitialScanState(INITIAL_SCAN_STATE_COMPLETE);
1797             } else {
1798                 mInitialPartialScanChannelCount = settings.channels.length;
1799             }
1800         }
1801         settings.type = WifiScanner.SCAN_TYPE_HIGH_ACCURACY; // always do high accuracy scans.
1802         settings.band = getScanBand(isFullBandScan);
1803         // Only enable RNR for full scans since we already have a known channel list for
1804         // partial scan. We do not want to enable RNR for partial scan since it could end up
1805         // wasting time scanning for 6Ghz APs that the device doesn't have credential to.
1806         if (SdkLevel.isAtLeastS()) {
1807             settings.setRnrSetting(isFullBandScan ? WifiScanner.WIFI_RNR_ENABLED
1808                     : WifiScanner.WIFI_RNR_NOT_NEEDED);
1809             settings.set6GhzPscOnlyEnabled(isFullBandScan
1810                     ? mContext.getResources().getBoolean(R.bool.config_wifiEnable6ghzPscScanning)
1811                     : false);
1812         }
1813         settings.reportEvents = WifiScanner.REPORT_EVENT_FULL_SCAN_RESULT
1814                             | WifiScanner.REPORT_EVENT_AFTER_EACH_SCAN;
1815         settings.numBssidsPerScan = 0;
1816         settings.hiddenNetworks.clear();
1817         // retrieve the list of hidden network SSIDs from saved network to scan for
1818         settings.hiddenNetworks.addAll(mConfigManager.retrieveHiddenNetworkList());
1819         // retrieve the list of hidden network SSIDs from Network suggestion to scan for
1820         settings.hiddenNetworks.addAll(mWifiNetworkSuggestionsManager.retrieveHiddenNetworkList());
1821 
1822         SingleScanListener singleScanListener =
1823                 new SingleScanListener(isFullBandScan);
1824         mScanner.startScan(
1825                 settings, new HandlerExecutor(mEventHandler), singleScanListener, workSource);
1826         mWifiMetrics.incrementConnectivityOneshotScanCount();
1827     }
1828 
startSingleScan(boolean isFullBandScan, WorkSource workSource)1829     private void startSingleScan(boolean isFullBandScan, WorkSource workSource) {
1830         if (!mWifiEnabled || !mAutoJoinEnabled) {
1831             return;
1832         }
1833         startForcedSingleScan(isFullBandScan, workSource);
1834     }
1835 
1836     // Start a periodic scan when screen is on
startPeriodicScan(boolean scanImmediately)1837     private void startPeriodicScan(boolean scanImmediately) {
1838         mPnoScanListener.resetLowRssiNetworkRetryDelay();
1839 
1840         // No connectivity scan if auto roaming is disabled.
1841         if (mWifiState == WIFI_STATE_CONNECTED && !mContext.getResources().getBoolean(
1842                 R.bool.config_wifi_framework_enable_associated_network_selection)) {
1843             return;
1844         }
1845 
1846         // Due to b/28020168, timer based single scan will be scheduled
1847         // to provide periodic scan in an exponential backoff fashion.
1848         if (scanImmediately) {
1849             resetLastPeriodicSingleScanTimeStamp();
1850         }
1851         mCurrentSingleScanScheduleIndex = 0;
1852         startPeriodicSingleScan();
1853     }
1854 
deviceMobilityStateToPnoScanIntervalMs(@eviceMobilityState int state)1855     private int deviceMobilityStateToPnoScanIntervalMs(@DeviceMobilityState int state) {
1856         switch (state) {
1857             case WifiManager.DEVICE_MOBILITY_STATE_UNKNOWN:
1858             case WifiManager.DEVICE_MOBILITY_STATE_LOW_MVMT:
1859             case WifiManager.DEVICE_MOBILITY_STATE_HIGH_MVMT:
1860                 return getScanIntervalWithPowerSaveMultiplier(mContext.getResources()
1861                         .getInteger(R.integer.config_wifiMovingPnoScanIntervalMillis));
1862             case WifiManager.DEVICE_MOBILITY_STATE_STATIONARY:
1863                 return getScanIntervalWithPowerSaveMultiplier(mContext.getResources()
1864                         .getInteger(R.integer.config_wifiStationaryPnoScanIntervalMillis));
1865             default:
1866                 return -1;
1867         }
1868     }
1869 
1870     /**
1871      * Pass device mobility state to WifiChannelUtilization and
1872      * alter the PNO scan interval based on the current device mobility state.
1873      * If the device is stationary, it will likely not find many new Wifi networks. Thus, increase
1874      * the interval between scans. Decrease the interval between scans if the device begins to move
1875      * again.
1876      * @param newState the new device mobility state
1877      */
setDeviceMobilityState(@eviceMobilityState int newState)1878     public void setDeviceMobilityState(@DeviceMobilityState int newState) {
1879         int oldDeviceMobilityState = mDeviceMobilityState;
1880         localLog("Device mobility state changed. state=" + newState);
1881         int newPnoScanIntervalMs = deviceMobilityStateToPnoScanIntervalMs(newState);
1882         if (newPnoScanIntervalMs < 0) {
1883             Log.e(TAG, "Invalid device mobility state: " + newState);
1884             return;
1885         }
1886         mDeviceMobilityState = newState;
1887         mWifiChannelUtilization.setDeviceMobilityState(newState);
1888 
1889         int oldPnoScanIntervalMs = deviceMobilityStateToPnoScanIntervalMs(oldDeviceMobilityState);
1890         if (newPnoScanIntervalMs == oldPnoScanIntervalMs) {
1891             if (mPnoScanStarted) {
1892                 mWifiMetrics.logPnoScanStop();
1893                 mWifiMetrics.enterDeviceMobilityState(newState);
1894                 mWifiMetrics.logPnoScanStart();
1895             } else {
1896                 mWifiMetrics.enterDeviceMobilityState(newState);
1897             }
1898         } else {
1899             Log.d(TAG, "PNO Scan Interval changed to " + newPnoScanIntervalMs + " ms.");
1900 
1901             if (mPnoScanStarted) {
1902                 Log.d(TAG, "Restarting PNO Scan with new scan interval");
1903                 stopPnoScan();
1904                 mWifiMetrics.enterDeviceMobilityState(newState);
1905                 startDisconnectedPnoScan();
1906             } else {
1907                 mWifiMetrics.enterDeviceMobilityState(newState);
1908             }
1909         }
1910     }
1911 
1912     // Start a DisconnectedPNO scan when screen is off and Wifi is disconnected
startDisconnectedPnoScan()1913     private void startDisconnectedPnoScan() {
1914         // Initialize PNO settings
1915         PnoSettings pnoSettings = new PnoSettings();
1916         List<PnoSettings.PnoNetwork> pnoNetworkList = retrievePnoNetworkList();
1917         int listSize = pnoNetworkList.size();
1918 
1919         if (listSize == 0) {
1920             // No saved network
1921             localLog("No saved network for starting disconnected PNO.");
1922             return;
1923         }
1924 
1925         pnoSettings.networkList = new PnoSettings.PnoNetwork[listSize];
1926         pnoSettings.networkList = pnoNetworkList.toArray(pnoSettings.networkList);
1927         pnoSettings.min6GHzRssi = mScoringParams.getEntryRssi(ScanResult.BAND_6_GHZ_START_FREQ_MHZ);
1928         pnoSettings.min5GHzRssi = mScoringParams.getEntryRssi(ScanResult.BAND_5_GHZ_START_FREQ_MHZ);
1929         pnoSettings.min24GHzRssi = mScoringParams.getEntryRssi(
1930                 ScanResult.BAND_24_GHZ_START_FREQ_MHZ);
1931 
1932         // Initialize scan settings
1933         ScanSettings scanSettings = new ScanSettings();
1934         scanSettings.band = getScanBand();
1935         scanSettings.reportEvents = WifiScanner.REPORT_EVENT_NO_BATCH;
1936         scanSettings.numBssidsPerScan = 0;
1937         scanSettings.periodInMs = deviceMobilityStateToPnoScanIntervalMs(mDeviceMobilityState);
1938 
1939         mScanner.startDisconnectedPnoScan(
1940                 scanSettings, pnoSettings, new HandlerExecutor(mEventHandler), mPnoScanListener);
1941         mPnoScanStarted = true;
1942         mWifiMetrics.logPnoScanStart();
1943     }
1944 
getAllScanOptimizationNetworks()1945     private @NonNull List<WifiConfiguration> getAllScanOptimizationNetworks() {
1946         List<WifiConfiguration> networks = mConfigManager.getSavedNetworks(-1);
1947         networks.addAll(mWifiNetworkSuggestionsManager.getAllScanOptimizationSuggestionNetworks());
1948         // remove all auto-join disabled or network selection disabled network.
1949         networks.removeIf(config -> !config.allowAutojoin
1950                 || !config.getNetworkSelectionStatus().isNetworkEnabled());
1951         return networks;
1952     }
1953 
1954     /**
1955      * Retrieve the PnoNetworks from Saved and suggestion non-passpoint network.
1956      */
1957     @VisibleForTesting
retrievePnoNetworkList()1958     public List<PnoSettings.PnoNetwork> retrievePnoNetworkList() {
1959         List<WifiConfiguration> networks = getAllScanOptimizationNetworks();
1960 
1961         if (networks.isEmpty()) {
1962             return Collections.EMPTY_LIST;
1963         }
1964         Collections.sort(networks, mConfigManager.getScanListComparator());
1965         boolean pnoFrequencyCullingEnabled = mContext.getResources()
1966                 .getBoolean(R.bool.config_wifiPnoFrequencyCullingEnabled);
1967 
1968         List<PnoSettings.PnoNetwork> pnoList = new ArrayList<>();
1969         Set<WifiScanner.PnoSettings.PnoNetwork> pnoSet = new HashSet<>();
1970         for (WifiConfiguration config : networks) {
1971             WifiScanner.PnoSettings.PnoNetwork pnoNetwork =
1972                     WifiConfigurationUtil.createPnoNetwork(config);
1973             if (pnoSet.contains(pnoNetwork)) {
1974                 continue;
1975             }
1976             pnoList.add(pnoNetwork);
1977             pnoSet.add(pnoNetwork);
1978             if (!pnoFrequencyCullingEnabled) {
1979                 continue;
1980             }
1981             Set<Integer> channelList = new HashSet<>();
1982             addChannelFromWifiScoreCard(channelList, config, 0,
1983                     MAX_PNO_SCAN_FREQUENCY_AGE_MS);
1984             pnoNetwork.frequencies = channelList.stream().mapToInt(Integer::intValue).toArray();
1985         }
1986         return pnoList;
1987     }
1988 
1989     // Stop PNO scan.
stopPnoScan()1990     private void stopPnoScan() {
1991         if (!mPnoScanStarted) return;
1992 
1993         mScanner.stopPnoScan(mPnoScanListener);
1994         mPnoScanStarted = false;
1995         mWifiMetrics.logPnoScanStop();
1996     }
1997 
1998     // Set up watchdog timer
scheduleWatchdogTimer()1999     private void scheduleWatchdogTimer() {
2000         localLog("scheduleWatchdogTimer");
2001 
2002         mAlarmManager.set(AlarmManager.ELAPSED_REALTIME_WAKEUP,
2003                             mClock.getElapsedSinceBootMillis() + mContext.getResources().getInteger(
2004                                     R.integer.config_wifiPnoWatchdogIntervalMs),
2005                             WATCHDOG_TIMER_TAG,
2006                             mWatchdogListener, mEventHandler);
2007         mWatchdogScanTimerSet = true;
2008     }
2009 
2010     // Cancel the watchdog scan timer.
cancelWatchdogScan()2011     private void cancelWatchdogScan() {
2012         if (mWatchdogScanTimerSet) {
2013             mAlarmManager.cancel(mWatchdogListener);
2014             mWatchdogScanTimerSet = false;
2015         }
2016     }
2017 
2018     // Schedules a delayed partial scan, which will scan the frequencies in mCachedWifiCandidates.
scheduleDelayedPartialScan(long delayMillis)2019     private void scheduleDelayedPartialScan(long delayMillis) {
2020         mAlarmManager.set(AlarmManager.ELAPSED_REALTIME_WAKEUP,
2021                 mClock.getElapsedSinceBootMillis() + delayMillis, DELAYED_PARTIAL_SCAN_TIMER_TAG,
2022                 mDelayedPartialScanTimerListener, mEventHandler);
2023         mDelayedPartialScanTimerSet = true;
2024     }
2025 
2026     // Cancel the delayed partial scan timer.
cancelDelayedPartialScan()2027     private void cancelDelayedPartialScan() {
2028         if (mDelayedPartialScanTimerSet) {
2029             mAlarmManager.cancel(mDelayedPartialScanTimerListener);
2030             mDelayedPartialScanTimerSet = false;
2031         }
2032     }
2033 
2034     // Set up periodic scan timer
schedulePeriodicScanTimer(int intervalMs)2035     private void schedulePeriodicScanTimer(int intervalMs) {
2036         mAlarmManager.set(AlarmManager.ELAPSED_REALTIME_WAKEUP,
2037                             mClock.getElapsedSinceBootMillis() + intervalMs,
2038                             PERIODIC_SCAN_TIMER_TAG,
2039                             mPeriodicScanTimerListener, mEventHandler);
2040         mPeriodicScanTimerSet = true;
2041     }
2042 
2043     // Cancel periodic scan timer
cancelPeriodicScanTimer()2044     private void cancelPeriodicScanTimer() {
2045         if (mPeriodicScanTimerSet) {
2046             mAlarmManager.cancel(mPeriodicScanTimerListener);
2047             mPeriodicScanTimerSet = false;
2048         }
2049     }
2050 
2051     // Set up timer to start a delayed single scan after RESTART_SCAN_DELAY_MS
scheduleDelayedSingleScan(boolean isFullBandScan)2052     private void scheduleDelayedSingleScan(boolean isFullBandScan) {
2053         localLog("scheduleDelayedSingleScan");
2054 
2055         RestartSingleScanListener restartSingleScanListener =
2056                 new RestartSingleScanListener(isFullBandScan);
2057         mAlarmManager.set(AlarmManager.ELAPSED_REALTIME_WAKEUP,
2058                             mClock.getElapsedSinceBootMillis() + RESTART_SCAN_DELAY_MS,
2059                             RESTART_SINGLE_SCAN_TIMER_TAG,
2060                             restartSingleScanListener, mEventHandler);
2061     }
2062 
2063     // Set up timer to start a delayed scan after msFromNow milli-seconds
scheduleDelayedConnectivityScan(int msFromNow)2064     private void scheduleDelayedConnectivityScan(int msFromNow) {
2065         localLog("scheduleDelayedConnectivityScan");
2066 
2067         mAlarmManager.set(AlarmManager.ELAPSED_REALTIME_WAKEUP,
2068                             mClock.getElapsedSinceBootMillis() + msFromNow,
2069                             RESTART_CONNECTIVITY_SCAN_TIMER_TAG,
2070                             mRestartScanListener, mEventHandler);
2071 
2072     }
2073 
2074     // Start a connectivity scan. The scan method is chosen according to
2075     // the current screen state and WiFi state.
startConnectivityScan(boolean scanImmediately)2076     private void startConnectivityScan(boolean scanImmediately) {
2077         localLog("startConnectivityScan: screenOn=" + mScreenOn
2078                 + " wifiState=" + stateToString(mWifiState)
2079                 + " scanImmediately=" + scanImmediately
2080                 + " wifiEnabled=" + mWifiEnabled
2081                 + " mAutoJoinEnabled=" + mAutoJoinEnabled);
2082 
2083         if (!mWifiEnabled || !mAutoJoinEnabled) {
2084             return;
2085         }
2086 
2087         // Always stop outstanding connectivity scan if there is any
2088         stopConnectivityScan();
2089 
2090         // Don't start a connectivity scan while Wifi is in the transition
2091         // between connected and disconnected states.
2092         if ((mWifiState != WIFI_STATE_CONNECTED && mWifiState != WIFI_STATE_DISCONNECTED)
2093                 || (getSingleScanningSchedule() == null)) {
2094             return;
2095         }
2096 
2097         if (mScreenOn) {
2098             startPeriodicScan(scanImmediately);
2099         } else {
2100             if (mWifiState == WIFI_STATE_DISCONNECTED && !mPnoScanStarted) {
2101                 startDisconnectedPnoScan();
2102             }
2103         }
2104     }
2105 
2106     // Stop connectivity scan if there is any.
stopConnectivityScan()2107     private void stopConnectivityScan() {
2108         // Due to b/28020168, timer based single scan will be scheduled
2109         // to provide periodic scan in an exponential backoff fashion.
2110         cancelPeriodicScanTimer();
2111         cancelDelayedPartialScan();
2112         stopPnoScan();
2113     }
2114 
2115     /**
2116      * Handler for screen state (on/off) changes
2117      */
handleScreenStateChanged(boolean screenOn)2118     private void handleScreenStateChanged(boolean screenOn) {
2119         localLog("handleScreenStateChanged: screenOn=" + screenOn);
2120 
2121         mScreenOn = screenOn;
2122 
2123         if (mWifiState == WIFI_STATE_DISCONNECTED
2124                 && mContext.getResources().getBoolean(R.bool.config_wifiEnablePartialInitialScan)) {
2125             setInitialScanState(INITIAL_SCAN_STATE_START);
2126         }
2127 
2128         mOpenNetworkNotifier.handleScreenStateChanged(screenOn);
2129 
2130         startConnectivityScan(SCAN_ON_SCHEDULE);
2131     }
2132 
2133     /**
2134      * Helper function that converts the WIFI_STATE_XXX constants to string
2135      */
stateToString(int state)2136     private static String stateToString(int state) {
2137         switch (state) {
2138             case WIFI_STATE_CONNECTED:
2139                 return "connected";
2140             case WIFI_STATE_DISCONNECTED:
2141                 return "disconnected";
2142             case WIFI_STATE_TRANSITIONING:
2143                 return "transitioning";
2144             default:
2145                 return "unknown";
2146         }
2147     }
2148 
2149     /**
2150      * Check if Single saved network schedule should be used
2151      * This is true if the one of the following is satisfied:
2152      * 1. Device has a total of 1 network whether saved, passpoint, or suggestion.
2153      * 2. The device is connected to that network.
2154      */
useSingleSavedNetworkSchedule()2155     private boolean useSingleSavedNetworkSchedule() {
2156         WifiConfiguration currentNetwork =
2157                 getPrimaryClientModeManager().getConnectedWifiConfiguration();
2158         if (currentNetwork == null) {
2159             localLog("Current network is missing, may caused by remove network and disconnecting");
2160             return false;
2161         }
2162         List<WifiConfiguration> savedNetworks =
2163                 mConfigManager.getSavedNetworks(Process.WIFI_UID);
2164         // If we have multiple saved networks, then no need to proceed
2165         if (savedNetworks.size() > 1) {
2166             return false;
2167         }
2168 
2169         List<PasspointConfiguration> passpointNetworks =
2170                 mPasspointManager.getProviderConfigs(Process.WIFI_UID, true);
2171         // If we have multiple networks (saved + passpoint), then no need to proceed
2172         if (passpointNetworks.size() + savedNetworks.size() > 1) {
2173             return false;
2174         }
2175 
2176         Set<WifiNetworkSuggestion> suggestionsNetworks =
2177                 mWifiNetworkSuggestionsManager.getAllApprovedNetworkSuggestions();
2178         // If total size not equal to 1, then no need to proceed
2179         if (passpointNetworks.size() + savedNetworks.size() + suggestionsNetworks.size() != 1) {
2180             return false;
2181         }
2182 
2183         // Next verify that this network is the one device is connected to
2184         int currentNetworkId = currentNetwork.networkId;
2185 
2186         // If we have a single saved network, and we are connected to it, return true.
2187         if (savedNetworks.size() == 1) {
2188             return (savedNetworks.get(0).networkId == currentNetworkId);
2189         }
2190 
2191         // If we have a single passpoint network, and we are connected to it, return true.
2192         if (passpointNetworks.size() == 1) {
2193             String passpointKey = passpointNetworks.get(0).getUniqueId();
2194             WifiConfiguration config = mConfigManager.getConfiguredNetwork(passpointKey);
2195             return (config != null && config.networkId == currentNetworkId);
2196         }
2197 
2198         // If we have a single suggestion network, and we are connected to it, return true.
2199         WifiNetworkSuggestion network = suggestionsNetworks.iterator().next();
2200         String suggestionKey = network.getWifiConfiguration().getProfileKey();
2201         WifiConfiguration config = mConfigManager.getConfiguredNetwork(suggestionKey);
2202         return (config != null && config.networkId == currentNetworkId);
2203     }
2204 
initSingleSavedNetworkSchedule()2205     private int[] initSingleSavedNetworkSchedule() {
2206         int[] schedule = mContext.getResources().getIntArray(
2207                     R.array.config_wifiSingleSavedNetworkConnectedScanIntervalScheduleSec);
2208         if (schedule == null || schedule.length == 0) {
2209             return null;
2210         }
2211 
2212         for (int val : schedule) {
2213             if (val <= 0) {
2214                 return null;
2215             }
2216         }
2217         return schedule;
2218     }
2219 
2220     /**
2221      * Handler for WiFi state (connected/disconnected) changes
2222      */
handleConnectionStateChanged( ConcreteClientModeManager clientModeManager, int state)2223     public void handleConnectionStateChanged(
2224             ConcreteClientModeManager clientModeManager, int state) {
2225         List<ClientModeManager> internetConnectivityCmms =
2226                 mActiveModeWarden.getInternetConnectivityClientModeManagers();
2227         if (!(internetConnectivityCmms.contains(clientModeManager))) {
2228             Log.w(TAG, "Ignoring call from non primary Mode Manager " + clientModeManager,
2229                     new Throwable());
2230             return;
2231         }
2232         localLog("handleConnectionStateChanged: state=" + stateToString(state));
2233 
2234         if (mConnectedSingleScanScheduleSec == null) {
2235             mConnectedSingleScanScheduleSec = initializeScanningSchedule(WIFI_STATE_CONNECTED);
2236         }
2237         if (mDisconnectedSingleScanScheduleSec == null) {
2238             mDisconnectedSingleScanScheduleSec =
2239                     initializeScanningSchedule(WIFI_STATE_DISCONNECTED);
2240         }
2241         if (mConnectedSingleSavedNetworkSingleScanScheduleSec == null) {
2242             mConnectedSingleSavedNetworkSingleScanScheduleSec =
2243                     initSingleSavedNetworkSchedule();
2244             if (mConnectedSingleSavedNetworkSingleScanScheduleSec == null) {
2245                 mConnectedSingleSavedNetworkSingleScanScheduleSec = mConnectedSingleScanScheduleSec;
2246             }
2247         }
2248 
2249         mWifiState = state;
2250 
2251         // Reset BSSID of last connection attempt and kick off
2252         // the watchdog timer if entering disconnected state.
2253         if (mWifiState == WIFI_STATE_DISCONNECTED) {
2254             scheduleWatchdogTimer();
2255             // Switch to the disconnected scanning schedule
2256             setSingleScanningSchedule(mDisconnectedSingleScanScheduleSec);
2257             startConnectivityScan(SCAN_IMMEDIATELY);
2258         } else if (mWifiState == WIFI_STATE_CONNECTED) {
2259             if (useSingleSavedNetworkSchedule()) {
2260                 // Switch to Single-Saved-Network connected schedule
2261                 setSingleScanningSchedule(mConnectedSingleSavedNetworkSingleScanScheduleSec);
2262             } else {
2263                 // Switch to connected single scanning schedule
2264                 setSingleScanningSchedule(mConnectedSingleScanScheduleSec);
2265             }
2266             startConnectivityScan(SCAN_ON_SCHEDULE);
2267         } else {
2268             // Intermediate state, no applicable single scanning schedule
2269             setSingleScanningSchedule(null);
2270             startConnectivityScan(SCAN_ON_SCHEDULE);
2271         }
2272     }
2273 
2274     /**
2275      * Handler when a WiFi connection attempt ended.
2276      *
2277      * @param failureCode {@link WifiMetrics.ConnectionEvent} failure code.
2278      * @param bssid the failed network.
2279      * @param ssid identifies the failed network.
2280      */
handleConnectionAttemptEnded(@onNull ActiveModeManager activeModeManager, int failureCode, @NonNull String bssid, @NonNull String ssid)2281     public void handleConnectionAttemptEnded(@NonNull ActiveModeManager activeModeManager,
2282             int failureCode, @NonNull String bssid, @NonNull String ssid) {
2283         List<ClientModeManager> internetConnectivityCmms =
2284                 mActiveModeWarden.getInternetConnectivityClientModeManagers();
2285         if (!(internetConnectivityCmms.contains(activeModeManager))) {
2286             Log.w(TAG, "Ignoring call from non primary Mode Manager " + activeModeManager,
2287                     new Throwable());
2288             return;
2289         }
2290         WifiInfo wifiInfo = getPrimaryWifiInfo();
2291         if (failureCode == WifiMetrics.ConnectionEvent.FAILURE_NONE) {
2292             String ssidUnquoted = (wifiInfo.getWifiSsid() == null)
2293                     ? null
2294                     : wifiInfo.getWifiSsid().toString();
2295             mOpenNetworkNotifier.handleWifiConnected(ssidUnquoted);
2296         } else {
2297             mOpenNetworkNotifier.handleConnectionFailure();
2298             retryConnectionOnLatestCandidates(bssid, ssid);
2299         }
2300     }
2301 
retryConnectionOnLatestCandidates(String bssid, String ssid)2302     private void retryConnectionOnLatestCandidates(String bssid, String ssid) {
2303         try {
2304             if (mLatestCandidates == null || mLatestCandidates.size() == 0
2305                     || mClock.getElapsedSinceBootMillis() - mLatestCandidatesTimestampMs
2306                     > TEMP_BSSID_BLOCK_DURATION) {
2307                 mLatestCandidates = null;
2308                 return;
2309             }
2310             MacAddress macAddress = MacAddress.fromString(bssid);
2311             int prevNumCandidates = mLatestCandidates.size();
2312             mLatestCandidates = mLatestCandidates.stream()
2313                     .filter(candidate -> {
2314                         // filter out the candidate with the BSSID that just failed
2315                         if (macAddress.equals(candidate.getKey().bssid)) {
2316                             return false;
2317                         }
2318                         // filter out candidates that are disabled.
2319                         WifiConfiguration config =
2320                                 mConfigManager.getConfiguredNetwork(candidate.getNetworkConfigId());
2321                         return config != null
2322                                 && config.getNetworkSelectionStatus().isNetworkEnabled();
2323                     })
2324                     .collect(Collectors.toList());
2325             if (prevNumCandidates == mLatestCandidates.size()) {
2326                 return;
2327             }
2328             WifiConfiguration candidate = mNetworkSelector.selectNetwork(mLatestCandidates);
2329             if (candidate != null) {
2330                 localLog("Automatic retry on the next best WNS candidate-" + candidate.SSID);
2331                 // Make sure that the failed BSSID is blocked for at least TEMP_BSSID_BLOCK_DURATION
2332                 // to prevent the supplicant from trying it again.
2333                 mWifiBlocklistMonitor.blockBssidForDurationMs(bssid, ssid,
2334                         TEMP_BSSID_BLOCK_DURATION,
2335                         WifiBlocklistMonitor.REASON_FRAMEWORK_DISCONNECT_FAST_RECONNECT, 0);
2336                 connectToNetworkForPrimaryCmmUsingMbbIfAvailable(candidate);
2337             }
2338         } catch (IllegalArgumentException e) {
2339             localLog("retryConnectionOnLatestCandidates: failed to create MacAddress from bssid="
2340                     + bssid);
2341             mLatestCandidates = null;
2342         }
2343     }
2344 
2345     // Enable auto-join if WifiConnectivityManager is enabled & we have any pending generic network
2346     // request (trusted or untrusted) and no specific network request in progress.
checkAllStatesAndEnableAutoJoin()2347     private void checkAllStatesAndEnableAutoJoin() {
2348         // if auto-join was disabled externally, don't re-enable for any triggers.
2349         // External triggers to disable always trumps any internal state.
2350         setAutoJoinEnabled(mAutoJoinEnabledExternal
2351                 && (mUntrustedConnectionAllowed || mOemPaidConnectionAllowed
2352                 || mOemPrivateConnectionAllowed || mTrustedConnectionAllowed)
2353                 && !mSpecificNetworkRequestInProgress);
2354         startConnectivityScan(SCAN_IMMEDIATELY);
2355     }
2356 
2357     /**
2358      * Triggered when {@link WifiNetworkFactory} has a pending general network request.
2359      */
setTrustedConnectionAllowed(boolean allowed)2360     public void setTrustedConnectionAllowed(boolean allowed) {
2361         localLog("setTrustedConnectionAllowed: allowed=" + allowed);
2362 
2363         if (mTrustedConnectionAllowed != allowed) {
2364             mTrustedConnectionAllowed = allowed;
2365             checkAllStatesAndEnableAutoJoin();
2366         }
2367     }
2368 
2369     /**
2370      * Triggered when {@link UntrustedWifiNetworkFactory} has a pending ephemeral network request.
2371      */
setUntrustedConnectionAllowed(boolean allowed)2372     public void setUntrustedConnectionAllowed(boolean allowed) {
2373         localLog("setUntrustedConnectionAllowed: allowed=" + allowed);
2374 
2375         if (mUntrustedConnectionAllowed != allowed) {
2376             mUntrustedConnectionAllowed = allowed;
2377             checkAllStatesAndEnableAutoJoin();
2378         }
2379     }
2380 
2381     /**
2382      * Triggered when {@link OemPaidWifiNetworkFactory} has a pending network request.
2383      */
setOemPaidConnectionAllowed(boolean allowed, WorkSource requestorWs)2384     public void setOemPaidConnectionAllowed(boolean allowed, WorkSource requestorWs) {
2385         localLog("setOemPaidConnectionAllowed: allowed=" + allowed + ", requestorWs="
2386                 + requestorWs);
2387 
2388         if (mOemPaidConnectionAllowed != allowed) {
2389             mOemPaidConnectionAllowed = allowed;
2390             mOemPaidConnectionRequestorWs = requestorWs;
2391             checkAllStatesAndEnableAutoJoin();
2392         }
2393     }
2394 
2395     /**
2396      * Triggered when {@link OemPrivateWifiNetworkFactory} has a pending network request.
2397      */
setOemPrivateConnectionAllowed(boolean allowed, WorkSource requestorWs)2398     public void setOemPrivateConnectionAllowed(boolean allowed, WorkSource requestorWs) {
2399         localLog("setOemPrivateConnectionAllowed: allowed=" + allowed + ", requestorWs="
2400                 + requestorWs);
2401 
2402         if (mOemPrivateConnectionAllowed != allowed) {
2403             mOemPrivateConnectionAllowed = allowed;
2404             mOemPrivateConnectionRequestorWs = requestorWs;
2405             checkAllStatesAndEnableAutoJoin();
2406         }
2407     }
2408 
2409     /**
2410      * Triggered when {@link WifiNetworkFactory} is processing a specific network request.
2411      */
setSpecificNetworkRequestInProgress(boolean inProgress)2412     public void setSpecificNetworkRequestInProgress(boolean inProgress) {
2413         localLog("setSpecificNetworkRequestInProgress : inProgress=" + inProgress);
2414 
2415         if (mSpecificNetworkRequestInProgress != inProgress) {
2416             mSpecificNetworkRequestInProgress = inProgress;
2417             checkAllStatesAndEnableAutoJoin();
2418         }
2419     }
2420 
2421     /**
2422      * Handler to prepare for connection to a user or app specified network
2423      */
prepareForForcedConnection(int netId)2424     public void prepareForForcedConnection(int netId) {
2425         WifiConfiguration config = mConfigManager.getConfiguredNetwork(netId);
2426         if (config == null) {
2427             return;
2428         }
2429         localLog("prepareForForcedConnection: SSID=" + config.SSID);
2430 
2431         clearConnectionAttemptTimeStamps();
2432         mWifiBlocklistMonitor.clearBssidBlocklistForSsid(config.SSID);
2433     }
2434 
2435     /**
2436      * Handler for on-demand connectivity scan
2437      */
forceConnectivityScan(WorkSource workSource)2438     public void forceConnectivityScan(WorkSource workSource) {
2439         if (!mWifiEnabled || !mRunning) return;
2440         localLog("forceConnectivityScan in request of " + workSource);
2441 
2442         clearConnectionAttemptTimeStamps();
2443         mWaitForFullBandScanResults = true;
2444         startForcedSingleScan(true, workSource);
2445     }
2446 
2447     /**
2448      * Helper method to populate WifiScanner handle. This is done lazily because
2449      * WifiScanningService is started after WifiService.
2450      */
retrieveWifiScanner()2451     private void retrieveWifiScanner() {
2452         if (mScanner != null) return;
2453         mScanner = Objects.requireNonNull(mContext.getSystemService(WifiScanner.class),
2454                 "Got a null instance of WifiScanner!");
2455         // Register for all single scan results
2456         mScanner.registerScanListener(new HandlerExecutor(mEventHandler), mAllSingleScanListener);
2457     }
2458 
2459     /**
2460      * Start WifiConnectivityManager
2461      */
start()2462     private void start() {
2463         if (mRunning) return;
2464         retrieveWifiScanner();
2465         mConnectivityHelper.getFirmwareRoamingInfo();
2466         mWifiChannelUtilization.init(getPrimaryClientModeManager().getWifiLinkLayerStats());
2467         clearConnectionAttemptTimeStamps(); // clear connection attempts.
2468 
2469         if (mContext.getResources().getBoolean(R.bool.config_wifiEnablePartialInitialScan)) {
2470             setInitialScanState(INITIAL_SCAN_STATE_START);
2471         }
2472 
2473         mRunning = true;
2474         mLatestCandidates = null;
2475         mLatestCandidatesTimestampMs = 0;
2476     }
2477 
2478     /**
2479      * Stop and reset WifiConnectivityManager
2480      */
stop()2481     private void stop() {
2482         if (!mRunning) return;
2483         mRunning = false;
2484         stopConnectivityScan();
2485         cancelWatchdogScan();
2486         resetLastPeriodicSingleScanTimeStamp();
2487         mOpenNetworkNotifier.clearPendingNotification(true /* resetRepeatDelay */);
2488         mWaitForFullBandScanResults = false;
2489         mLatestCandidates = null;
2490         mLatestCandidatesTimestampMs = 0;
2491         mScanRestartCount = 0;
2492     }
2493 
2494     /**
2495      * Update WifiConnectivityManager running state
2496      *
2497      * Start WifiConnectivityManager only if both Wifi and WifiConnectivityManager
2498      * are enabled, otherwise stop it.
2499      */
updateRunningState()2500     private void updateRunningState() {
2501         if (mWifiEnabled && mAutoJoinEnabled) {
2502             localLog("Starting up WifiConnectivityManager");
2503             start();
2504         } else {
2505             localLog("Stopping WifiConnectivityManager");
2506             stop();
2507         }
2508     }
2509 
2510     /**
2511      * Inform WiFi is enabled for connection or not
2512      */
setWifiEnabled(boolean enable)2513     private void setWifiEnabled(boolean enable) {
2514         if (mWifiEnabled == enable) return;
2515 
2516         localLog("Set WiFi " + (enable ? "enabled" : "disabled"));
2517 
2518         if (!enable) {
2519             mNetworkSelector.resetOnDisable();
2520             mConfigManager.enableTemporaryDisabledNetworks();
2521             mConfigManager.stopRestrictingAutoJoinToSubscriptionId();
2522             mConfigManager.clearUserTemporarilyDisabledList();
2523             mConfigManager.removeAllEphemeralOrPasspointConfiguredNetworks();
2524             // Flush ANQP cache if configured to do so
2525             if (mWifiGlobals.flushAnqpCacheOnWifiToggleOffEvent()) {
2526                 mPasspointManager.clearAnqpRequestsAndFlushCache();
2527             }
2528         }
2529         mWifiEnabled = enable;
2530         updateRunningState();
2531     }
2532 
2533     /**
2534      * Turn on/off the WifiConnectivityManager at runtime
2535      */
setAutoJoinEnabled(boolean enable)2536     private void setAutoJoinEnabled(boolean enable) {
2537         mAutoJoinEnabled = enable;
2538         updateRunningState();
2539     }
2540 
2541     /**
2542      * Turn on/off the auto join at runtime
2543      */
setAutoJoinEnabledExternal(boolean enable)2544     public void setAutoJoinEnabledExternal(boolean enable) {
2545         localLog("Set auto join " + (enable ? "enabled" : "disabled"));
2546 
2547         if (mAutoJoinEnabledExternal != enable) {
2548             mAutoJoinEnabledExternal = enable;
2549             checkAllStatesAndEnableAutoJoin();
2550         }
2551     }
2552 
2553     @VisibleForTesting
getLowRssiNetworkRetryDelay()2554     int getLowRssiNetworkRetryDelay() {
2555         return mPnoScanListener.getLowRssiNetworkRetryDelay();
2556     }
2557 
2558     @VisibleForTesting
getLastPeriodicSingleScanTimeStamp()2559     long getLastPeriodicSingleScanTimeStamp() {
2560         return mLastPeriodicSingleScanTimeStamp;
2561     }
2562 
2563     /**
2564      * Dump the local logs.
2565      */
dump(FileDescriptor fd, PrintWriter pw, String[] args)2566     public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
2567         pw.println("Dump of WifiConnectivityManager");
2568         pw.println("WifiConnectivityManager - Log Begin ----");
2569         mLocalLog.dump(fd, pw, args);
2570         pw.println("WifiConnectivityManager - Log End ----");
2571         mOpenNetworkNotifier.dump(fd, pw, args);
2572         mWifiBlocklistMonitor.dump(fd, pw, args);
2573     }
2574 }
2575