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