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