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