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