• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright 2018 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 android.annotation.NonNull;
20 import android.annotation.Nullable;
21 import android.content.Context;
22 import android.net.MacAddress;
23 import android.net.wifi.ScanResult;
24 import android.net.wifi.SecurityParams;
25 import android.net.wifi.WifiAnnotations;
26 import android.net.wifi.WifiConfiguration;
27 import android.util.ArrayMap;
28 
29 import com.android.internal.util.Preconditions;
30 import com.android.server.wifi.proto.WifiScoreCardProto;
31 
32 import java.util.ArrayList;
33 import java.util.Collection;
34 import java.util.Collections;
35 import java.util.List;
36 import java.util.Map;
37 import java.util.Objects;
38 import java.util.StringJoiner;
39 import java.util.stream.Collectors;
40 
41 /**
42  * Candidates for network selection
43  */
44 public class WifiCandidates {
45     private static final String TAG = "WifiCandidates";
46 
WifiCandidates(@onNull WifiScoreCard wifiScoreCard, @NonNull Context context)47     public WifiCandidates(@NonNull WifiScoreCard wifiScoreCard, @NonNull Context context) {
48         this(wifiScoreCard, context, Collections.EMPTY_LIST);
49     }
50 
WifiCandidates(@onNull WifiScoreCard wifiScoreCard, @NonNull Context context, @NonNull List<Candidate> candidates)51     public WifiCandidates(@NonNull WifiScoreCard wifiScoreCard, @NonNull Context context,
52             @NonNull List<Candidate> candidates) {
53         mWifiScoreCard = Preconditions.checkNotNull(wifiScoreCard);
54         mContext = context;
55         for (Candidate c : candidates) {
56             mCandidates.put(c.getKey(), c);
57         }
58     }
59 
60     private final WifiScoreCard mWifiScoreCard;
61     private final Context mContext;
62 
63     /**
64      * Represents a connectable candidate.
65      */
66     public interface Candidate {
67         /**
68          * Gets the Key, which contains the SSID, BSSID, security type, and config id.
69          *
70          * Generally, a CandidateScorer should not need to use this.
71          */
getKey()72         @Nullable Key getKey();
73 
74         /**
75          * Gets the config id.
76          */
getNetworkConfigId()77         int getNetworkConfigId();
78         /**
79          * Returns true for an open network.
80          */
isOpenNetwork()81         boolean isOpenNetwork();
82         /**
83          * Returns true for a passpoint network.
84          */
isPasspoint()85         boolean isPasspoint();
86         /**
87          * Returns true for an ephemeral network.
88          */
isEphemeral()89         boolean isEphemeral();
90         /**
91          * Returns true for a trusted network.
92          */
isTrusted()93         boolean isTrusted();
94         /**
95          * Returns true for a oem paid network.
96          */
isOemPaid()97         boolean isOemPaid();
98         /**
99          * Returns true for a oem private network.
100          */
isOemPrivate()101         boolean isOemPrivate();
102 
103         /**
104          * Returns true if suggestion came from a carrier or privileged app.
105          */
isCarrierOrPrivileged()106         boolean isCarrierOrPrivileged();
107         /**
108          * Returns true for a metered network.
109          */
isMetered()110         boolean isMetered();
111 
112         /**
113          * Returns true if network doesn't have internet access during last connection
114          */
hasNoInternetAccess()115         boolean hasNoInternetAccess();
116 
117         /**
118          * Returns true if network is expected not to have Internet access
119          * (e.g., a wireless printer, a Chromecast hotspot, etc.).
120          */
isNoInternetAccessExpected()121         boolean isNoInternetAccessExpected();
122 
123         /**
124          * Returns the ID of the nominator that provided the candidate.
125          */
126         @WifiNetworkSelector.NetworkNominator.NominatorId
getNominatorId()127         int getNominatorId();
128 
129         /**
130          * Returns true if the candidate is in the same network as the
131          * current connection.
132          */
isCurrentNetwork()133         boolean isCurrentNetwork();
134         /**
135          * Return true if the candidate is currently connected.
136          */
isCurrentBssid()137         boolean isCurrentBssid();
138         /**
139          * Returns a value between 0 and 1.
140          *
141          * 1.0 means the network was recently selected by the user or an app.
142          * 0.0 means not recently selected by user or app.
143          */
getLastSelectionWeight()144         double getLastSelectionWeight();
145         /**
146          * Returns true if the network was selected by the user.
147          */
isUserSelected()148         boolean isUserSelected();
149         /**
150          * Gets the scan RSSI.
151          */
getScanRssi()152         int getScanRssi();
153         /**
154          * Gets the scan frequency.
155          */
getFrequency()156         int getFrequency();
157 
158         /**
159          * Gets the channel width.
160          */
getChannelWidth()161         @WifiAnnotations.ChannelWidth int getChannelWidth();
162         /**
163          * Gets the predicted throughput in Mbps.
164          */
getPredictedThroughputMbps()165         int getPredictedThroughputMbps();
166 
167         /**
168          * Gets the predicted multi-link throughput in Mbps.
169          */
getPredictedMultiLinkThroughputMbps()170         int getPredictedMultiLinkThroughputMbps();
171 
172         /**
173          * Sets the predicted multi-link throughput in Mbps.
174          */
setPredictedMultiLinkThroughputMbps(int throughput)175         void setPredictedMultiLinkThroughputMbps(int throughput);
176 
177         /**
178          * Estimated probability of getting internet access (percent 0-100).
179          */
getEstimatedPercentInternetAvailability()180         int getEstimatedPercentInternetAvailability();
181 
182         /**
183          * If the candidate is MLO capable, return the AP MLD MAC address.
184          *
185          * @return Mac address of the AP MLD.
186          */
getApMldMacAddress()187         MacAddress getApMldMacAddress();
188 
189         /**
190          * Gets statistics from the scorecard.
191          */
getEventStatistics(WifiScoreCardProto.Event event)192         @Nullable WifiScoreCardProto.Signal getEventStatistics(WifiScoreCardProto.Event event);
193 
194         /**
195          * Returns true for a restricted network.
196          */
isRestricted()197         boolean isRestricted();
198 
199         /**
200          * Returns true if the candidate is a multi-link capable.
201          *
202          * @return true or false.
203          */
isMultiLinkCapable()204         boolean isMultiLinkCapable();
205     }
206 
207     /**
208      * Represents a connectable candidate
209      */
210     private static class CandidateImpl implements Candidate {
211         private final Key mKey;                   // SSID/sectype/BSSID/configId
212         private final @WifiNetworkSelector.NetworkNominator.NominatorId int mNominatorId;
213         private final int mScanRssi;
214         private final int mFrequency;
215         private final int mChannelWidth;
216         private final double mLastSelectionWeight;
217         private final WifiScoreCard.PerBssid mPerBssid; // For accessing the scorecard entry
218         private final boolean mIsUserSelected;
219         private final boolean mIsCurrentNetwork;
220         private final boolean mIsCurrentBssid;
221         private final boolean mIsMetered;
222         private final boolean mHasNoInternetAccess;
223         private final boolean mIsNoInternetAccessExpected;
224         private final boolean mIsOpenNetwork;
225         private final boolean mPasspoint;
226         private final boolean mEphemeral;
227         private final boolean mTrusted;
228         private final boolean mRestricted;
229         private final boolean mOemPaid;
230         private final boolean mOemPrivate;
231         private final boolean mCarrierOrPrivileged;
232         private final int mPredictedThroughputMbps;
233         private int mPredictedMultiLinkThroughputMbps;
234         private final int mEstimatedPercentInternetAvailability;
235         private final MacAddress mApMldMacAddress;
236 
CandidateImpl(Key key, WifiConfiguration config, WifiScoreCard.PerBssid perBssid, @WifiNetworkSelector.NetworkNominator.NominatorId int nominatorId, int scanRssi, int frequency, int channelWidth, double lastSelectionWeight, boolean isUserSelected, boolean isCurrentNetwork, boolean isCurrentBssid, boolean isMetered, boolean isCarrierOrPrivileged, int predictedThroughputMbps, MacAddress apMldMacAddress)237         CandidateImpl(Key key, WifiConfiguration config,
238                 WifiScoreCard.PerBssid perBssid,
239                 @WifiNetworkSelector.NetworkNominator.NominatorId int nominatorId,
240                 int scanRssi,
241                 int frequency,
242                 int channelWidth,
243                 double lastSelectionWeight,
244                 boolean isUserSelected,
245                 boolean isCurrentNetwork,
246                 boolean isCurrentBssid,
247                 boolean isMetered,
248                 boolean isCarrierOrPrivileged,
249                 int predictedThroughputMbps,
250                 MacAddress apMldMacAddress) {
251             this.mKey = key;
252             this.mNominatorId = nominatorId;
253             this.mScanRssi = scanRssi;
254             this.mFrequency = frequency;
255             this.mChannelWidth = channelWidth;
256             this.mPerBssid = perBssid;
257             this.mLastSelectionWeight = lastSelectionWeight;
258             this.mIsUserSelected = isUserSelected;
259             this.mIsCurrentNetwork = isCurrentNetwork;
260             this.mIsCurrentBssid = isCurrentBssid;
261             this.mIsMetered = isMetered;
262             this.mHasNoInternetAccess = config.hasNoInternetAccess();
263             this.mIsNoInternetAccessExpected = config.isNoInternetAccessExpected();
264             this.mIsOpenNetwork = WifiConfigurationUtil.isConfigForOpenNetwork(config);
265             this.mPasspoint = config.isPasspoint();
266             this.mEphemeral = config.isEphemeral();
267             this.mTrusted = config.trusted;
268             this.mOemPaid = config.oemPaid;
269             this.mOemPrivate = config.oemPrivate;
270             this.mCarrierOrPrivileged = isCarrierOrPrivileged;
271             this.mPredictedThroughputMbps = predictedThroughputMbps;
272             this.mEstimatedPercentInternetAvailability = perBssid == null ? 50 :
273                     perBssid.estimatePercentInternetAvailability();
274             this.mRestricted = config.restricted;
275             this.mPredictedMultiLinkThroughputMbps = 0;
276             this.mApMldMacAddress = apMldMacAddress;
277         }
278 
279         @Override
getKey()280         public Key getKey() {
281             return mKey;
282         }
283 
284         @Override
getNetworkConfigId()285         public int getNetworkConfigId() {
286             return mKey.networkId;
287         }
288 
289         @Override
isOpenNetwork()290         public boolean isOpenNetwork() {
291             return mIsOpenNetwork;
292         }
293 
294         @Override
isPasspoint()295         public boolean isPasspoint() {
296             return mPasspoint;
297         }
298 
299         @Override
isEphemeral()300         public boolean isEphemeral() {
301             return mEphemeral;
302         }
303 
304         @Override
isTrusted()305         public boolean isTrusted() {
306             return mTrusted;
307         }
308 
309         @Override
isRestricted()310         public boolean isRestricted() {
311             return mRestricted;
312         }
313 
314         @Override
isMultiLinkCapable()315         public boolean isMultiLinkCapable() {
316             return (mApMldMacAddress != null);
317         }
318 
319         @Override
isOemPaid()320         public boolean isOemPaid() {
321             return mOemPaid;
322         }
323 
324         @Override
isOemPrivate()325         public boolean isOemPrivate() {
326             return mOemPrivate;
327         }
328 
329         @Override
isCarrierOrPrivileged()330         public boolean isCarrierOrPrivileged() {
331             return mCarrierOrPrivileged;
332         }
333 
334         @Override
isMetered()335         public boolean isMetered() {
336             return mIsMetered;
337         }
338 
339         @Override
hasNoInternetAccess()340         public boolean hasNoInternetAccess() {
341             return mHasNoInternetAccess;
342         }
343 
344         @Override
isNoInternetAccessExpected()345         public boolean isNoInternetAccessExpected() {
346             return mIsNoInternetAccessExpected;
347         }
348 
349         @Override
getNominatorId()350         public @WifiNetworkSelector.NetworkNominator.NominatorId int getNominatorId() {
351             return mNominatorId;
352         }
353 
354         @Override
getLastSelectionWeight()355         public double getLastSelectionWeight() {
356             return mLastSelectionWeight;
357         }
358 
359         @Override
isUserSelected()360         public boolean isUserSelected() {
361             return mIsUserSelected;
362         }
363 
364         @Override
isCurrentNetwork()365         public boolean isCurrentNetwork() {
366             return mIsCurrentNetwork;
367         }
368 
369         @Override
isCurrentBssid()370         public boolean isCurrentBssid() {
371             return mIsCurrentBssid;
372         }
373 
374         @Override
getScanRssi()375         public int getScanRssi() {
376             return mScanRssi;
377         }
378 
379         @Override
getFrequency()380         public int getFrequency() {
381             return mFrequency;
382         }
383 
384         @Override
getChannelWidth()385         public int getChannelWidth() {
386             return mChannelWidth;
387         }
388 
389         @Override
getPredictedThroughputMbps()390         public int getPredictedThroughputMbps() {
391             return mPredictedThroughputMbps;
392         }
393 
394         @Override
getPredictedMultiLinkThroughputMbps()395         public int getPredictedMultiLinkThroughputMbps() {
396             return mPredictedMultiLinkThroughputMbps;
397         }
398 
399         @Override
setPredictedMultiLinkThroughputMbps(int throughput)400         public void setPredictedMultiLinkThroughputMbps(int throughput) {
401             mPredictedMultiLinkThroughputMbps = throughput;
402         }
403 
404         @Override
getEstimatedPercentInternetAvailability()405         public int getEstimatedPercentInternetAvailability() {
406             return mEstimatedPercentInternetAvailability;
407         }
408 
409         @Override
getApMldMacAddress()410         public MacAddress getApMldMacAddress() {
411             return  mApMldMacAddress;
412         }
413 
414         /**
415          * Accesses statistical information from the score card
416          */
417         @Override
getEventStatistics(WifiScoreCardProto.Event event)418         public WifiScoreCardProto.Signal getEventStatistics(WifiScoreCardProto.Event event) {
419             if (mPerBssid == null) return null;
420             WifiScoreCard.PerSignal perSignal = mPerBssid.lookupSignal(event, getFrequency());
421             if (perSignal == null) return null;
422             return perSignal.toSignal();
423         }
424 
425         @Override
toString()426         public String toString() {
427             Key key = getKey();
428             String lastSelectionWeightString = "";
429             if (getLastSelectionWeight() != 0.0) {
430                 // Round this to 3 places
431                 lastSelectionWeightString = "lastSelectionWeight = "
432                         + Math.round(getLastSelectionWeight() * 1000.0) / 1000.0
433                         + ", ";
434             }
435             return "Candidate { "
436                     + "config = " + getNetworkConfigId() + ", "
437                     + "bssid = " + key.bssid + ", "
438                     + "freq = " + getFrequency() + ", "
439                     + "channelWidth = " + getChannelWidth() + ", "
440                     + "rssi = " + getScanRssi() + ", "
441                     + "Mbps = " + getPredictedThroughputMbps() + ", "
442                     + "nominator = " + getNominatorId() + ", "
443                     + "pInternet = " + getEstimatedPercentInternetAvailability() + ", "
444                     + lastSelectionWeightString
445                     + (isCurrentBssid() ? "connected, " : "")
446                     + (isCurrentNetwork() ? "current, " : "")
447                     + (isEphemeral() ? "ephemeral" : "saved") + ", "
448                     + (isTrusted() ? "trusted, " : "")
449                     + (isRestricted() ? "restricted, " : "")
450                     + (isOemPaid() ? "oemPaid, " : "")
451                     + (isOemPrivate() ? "oemPrivate, " : "")
452                     + (isCarrierOrPrivileged() ? "priv, " : "")
453                     + (isMetered() ? "metered, " : "")
454                     + (hasNoInternetAccess() ? "noInternet, " : "")
455                     + (isNoInternetAccessExpected() ? "noInternetExpected, " : "")
456                     + (isPasspoint() ? "passpoint, " : "")
457                     + (isOpenNetwork() ? "open" : "secure") + " }";
458         }
459     }
460 
461     /**
462      * Represents a scoring function
463      */
464     public interface CandidateScorer {
465         /**
466          * The scorer's name, and perhaps important parameterization/version.
467          */
getIdentifier()468         String getIdentifier();
469 
470         /**
471          * Calculates the best score for a collection of candidates.
472          */
scoreCandidates(@onNull Collection<Candidate> candidates)473         @Nullable ScoredCandidate scoreCandidates(@NonNull Collection<Candidate> candidates);
474 
475     }
476 
477     /**
478      * Represents a candidate with a real-valued score, along with an error estimate.
479      *
480      * Larger values reflect more desirable candidates. The range is arbitrary,
481      * because scores generated by different sources are not compared with each
482      * other.
483      *
484      * The error estimate is on the same scale as the value, and should
485      * always be strictly positive. For instance, it might be the standard deviation.
486      */
487     public static class ScoredCandidate {
488         public final double value;
489         public final double err;
490         public final Key candidateKey;
491         public final boolean userConnectChoiceOverride;
ScoredCandidate(double value, double err, boolean userConnectChoiceOverride, Candidate candidate)492         public ScoredCandidate(double value, double err, boolean userConnectChoiceOverride,
493                 Candidate candidate) {
494             this.value = value;
495             this.err = err;
496             this.candidateKey = (candidate == null) ? null : candidate.getKey();
497             this.userConnectChoiceOverride = userConnectChoiceOverride;
498         }
499         /**
500          * Represents no score
501          */
502         public static final ScoredCandidate NONE =
503                 new ScoredCandidate(Double.NEGATIVE_INFINITY, Double.POSITIVE_INFINITY,
504                         false, null);
505     }
506 
507     /**
508      * The key used for tracking candidates, consisting of SSID, security type, BSSID, and network
509      * configuration id.
510      */
511     // TODO (b/123014687) unify with similar classes in the framework
512     public static class Key {
513         public final ScanResultMatchInfo matchInfo; // Contains the SSID and security type
514         public final MacAddress bssid;
515         public final int networkId;                 // network configuration id
516         public final @WifiConfiguration.SecurityType int securityType;
517 
Key(ScanResultMatchInfo matchInfo, MacAddress bssid, int networkId)518         public Key(ScanResultMatchInfo matchInfo,
519                    MacAddress bssid,
520                    int networkId) {
521             this.matchInfo = matchInfo;
522             this.bssid = bssid;
523             this.networkId = networkId;
524             // If security type is not set, use the default security params.
525             this.securityType = matchInfo.getDefaultSecurityParams().getSecurityType();
526         }
527 
Key(ScanResultMatchInfo matchInfo, MacAddress bssid, int networkId, int securityType)528         public Key(ScanResultMatchInfo matchInfo,
529                    MacAddress bssid,
530                    int networkId,
531                    int securityType) {
532             this.matchInfo = matchInfo;
533             this.bssid = bssid;
534             this.networkId = networkId;
535             this.securityType = securityType;
536         }
537 
538         @Override
equals(Object other)539         public boolean equals(Object other) {
540             if (!(other instanceof Key)) return false;
541             Key that = (Key) other;
542             return (this.matchInfo.equals(that.matchInfo)
543                     && this.bssid.equals(that.bssid)
544                     && this.networkId == that.networkId
545                     && this.securityType == that.securityType);
546         }
547 
548         @Override
hashCode()549         public int hashCode() {
550             return Objects.hash(matchInfo, bssid, networkId, securityType);
551         }
552     }
553 
554     private final Map<Key, Candidate> mCandidates = new ArrayMap<>();
555 
556     /**
557      * Lists of multi-link candidates mapped with MLD mac address.
558      *
559      * e.g. let's say we have 10 candidates starting from Candidate_1 to Candidate_10.
560      *  mMultiLinkCandidates has a mapping,
561      *      BSSID_MLD_AP1 -> [Candidate_1, Candidate_3]
562      *      BSSID_MLD_AP2 -> [Candidate_4, Candidate_6, Candidate_7]
563      *      Here, Candidate_1 and _3 are the affiliated to MLD_AP1.
564      *            Candidate_4, _6, _7 are affiliated to MLD_AP2
565      *  All remaining candidates are not affiliated to any MLD AP's.
566      */
567     private final Map<MacAddress, List<Candidate>> mMultiLinkCandidates = new ArrayMap<>();
568 
569     /**
570      * Get a list of multi-link candidates as a collection.
571      *
572      * @return List of candidates or empty Collection if none present.
573      */
getMultiLinkCandidates()574     public Collection<List<Candidate>> getMultiLinkCandidates() {
575         return mMultiLinkCandidates.values();
576     }
577 
578     /**
579      * Get a list of multi-link candidates for a particular MLD AP.
580      *
581      * @param mldMacAddr AP MLD address.
582      * @return List of candidates or null if none present.
583      */
584     @Nullable
getMultiLinkCandidates(@onNull MacAddress mldMacAddr)585     public List<Candidate> getMultiLinkCandidates(@NonNull MacAddress mldMacAddr) {
586         return mMultiLinkCandidates.get(mldMacAddr);
587     }
588 
589     private int mCurrentNetworkId = -1;
590     @Nullable private MacAddress mCurrentBssid = null;
591 
592     /**
593      * Sets up information about the currently-connected network.
594      */
setCurrent(int currentNetworkId, String currentBssid)595     public void setCurrent(int currentNetworkId, String currentBssid) {
596         mCurrentNetworkId = currentNetworkId;
597         mCurrentBssid = null;
598         if (currentBssid == null) return;
599         try {
600             mCurrentBssid = MacAddress.fromString(currentBssid);
601         } catch (RuntimeException e) {
602             failWithException(e);
603         }
604     }
605 
606     /**
607      * Adds a new candidate
608      *
609      * @return true if added or replaced, false otherwise
610      */
add(ScanDetail scanDetail, WifiConfiguration config, @WifiNetworkSelector.NetworkNominator.NominatorId int nominatorId, double lastSelectionWeightBetweenZeroAndOne, boolean isMetered, int predictedThroughputMbps)611     public boolean add(ScanDetail scanDetail,
612             WifiConfiguration config,
613             @WifiNetworkSelector.NetworkNominator.NominatorId int nominatorId,
614             double lastSelectionWeightBetweenZeroAndOne,
615             boolean isMetered,
616             int predictedThroughputMbps) {
617         Key key = keyFromScanDetailAndConfig(scanDetail, config);
618         if (key == null) return false;
619         return add(key, config, nominatorId,
620                 scanDetail.getScanResult().level,
621                 scanDetail.getScanResult().frequency,
622                 scanDetail.getScanResult().channelWidth,
623                 lastSelectionWeightBetweenZeroAndOne,
624                 isMetered,
625                 false,
626                 predictedThroughputMbps,
627                 scanDetail.getScanResult().getApMldMacAddress());
628     }
629 
630     /**
631      * Makes a Key from a ScanDetail and WifiConfiguration (null if error).
632      */
keyFromScanDetailAndConfig(ScanDetail scanDetail, WifiConfiguration config)633     public @Nullable Key keyFromScanDetailAndConfig(ScanDetail scanDetail,
634             WifiConfiguration config) {
635         if (!validConfigAndScanDetail(config, scanDetail)) return null;
636 
637         ScanResult scanResult = scanDetail.getScanResult();
638         SecurityParams params = ScanResultMatchInfo.fromScanResult(scanResult)
639                 .matchForNetworkSelection(ScanResultMatchInfo.fromWifiConfiguration(config));
640         if (null == params) return null;
641         MacAddress bssid = MacAddress.fromString(scanResult.BSSID);
642         return new Key(ScanResultMatchInfo.fromScanResult(scanResult), bssid, config.networkId,
643                 params.getSecurityType());
644     }
645 
646     /**
647      * Adds a new candidate
648      *
649      * @return true if added or replaced, false otherwise
650      */
add(@onNull Key key, WifiConfiguration config, @WifiNetworkSelector.NetworkNominator.NominatorId int nominatorId, int scanRssi, int frequency, @WifiAnnotations.ChannelWidth int channelWidth, double lastSelectionWeightBetweenZeroAndOne, boolean isMetered, boolean isCarrierOrPrivileged, int predictedThroughputMbps, MacAddress apMldMacAddress)651     public boolean add(@NonNull Key key,
652             WifiConfiguration config,
653             @WifiNetworkSelector.NetworkNominator.NominatorId int nominatorId,
654             int scanRssi,
655             int frequency,
656             @WifiAnnotations.ChannelWidth int channelWidth,
657             double lastSelectionWeightBetweenZeroAndOne,
658             boolean isMetered,
659             boolean isCarrierOrPrivileged,
660             int predictedThroughputMbps,
661             MacAddress apMldMacAddress) {
662         Candidate old = mCandidates.get(key);
663         if (old != null) {
664             // check if we want to replace this old candidate
665             if (nominatorId > old.getNominatorId()) return false;
666             remove(old);
667         }
668         WifiScoreCard.PerBssid perBssid = mWifiScoreCard.lookupBssid(
669                 key.matchInfo.networkSsid,
670                 key.bssid.toString());
671         perBssid.setSecurityType(
672                 WifiScoreCardProto.SecurityType.forNumber(
673                     key.matchInfo.getDefaultSecurityParams().getSecurityType()));
674         perBssid.setNetworkConfigId(config.networkId);
675         CandidateImpl candidate = new CandidateImpl(key, config, perBssid, nominatorId,
676                 scanRssi,
677                 frequency,
678                 channelWidth,
679                 Math.min(Math.max(lastSelectionWeightBetweenZeroAndOne, 0.0), 1.0),
680                 config.isUserSelected(),
681                 config.networkId == mCurrentNetworkId,
682                 key.bssid.equals(mCurrentBssid),
683                 isMetered,
684                 isCarrierOrPrivileged,
685                 predictedThroughputMbps,
686                 apMldMacAddress);
687         mCandidates.put(key, candidate);
688         if (apMldMacAddress != null) {
689             List<Candidate> mlCandidates = mMultiLinkCandidates.computeIfAbsent(apMldMacAddress,
690                     k -> new ArrayList<>());
691             mlCandidates.add(candidate);
692         }
693         return true;
694     }
695 
696     /**
697      * Checks that the supplied config and scan detail are valid (for the parts
698      * we care about) and consistent with each other.
699      *
700      * @param config to be validated
701      * @param scanDetail to be validated
702      * @return true if the config and scanDetail are consistent with each other
703      */
validConfigAndScanDetail(WifiConfiguration config, ScanDetail scanDetail)704     private boolean validConfigAndScanDetail(WifiConfiguration config, ScanDetail scanDetail) {
705         if (config == null) return failure();
706         if (scanDetail == null) return failure();
707         ScanResult scanResult = scanDetail.getScanResult();
708         if (scanResult == null) return failure();
709         MacAddress bssid;
710         try {
711             bssid = MacAddress.fromString(scanResult.BSSID);
712         } catch (RuntimeException e) {
713             return failWithException(e);
714         }
715         ScanResultMatchInfo key1 = ScanResultMatchInfo.fromScanResult(scanResult);
716         if (!config.isPasspoint()) {
717             ScanResultMatchInfo key2 = ScanResultMatchInfo.fromWifiConfiguration(config);
718             if (!key1.equals(key2)) {
719                 return failure(key1, key2);
720             }
721         }
722         return true;
723     }
724 
725     /**
726      * Removes a candidate
727      * @return true if the candidate was successfully removed
728      */
remove(Candidate candidate)729     public boolean remove(Candidate candidate) {
730         if (!(candidate instanceof CandidateImpl)) return failure();
731         return mCandidates.remove(candidate.getKey(), candidate);
732     }
733 
734     /**
735      * Returns the number of candidates (at the BSSID level)
736      */
size()737     public int size() {
738         return mCandidates.size();
739     }
740 
741     /**
742      * Returns the candidates, grouped by network.
743      */
getGroupedCandidates()744     public Collection<Collection<Candidate>> getGroupedCandidates() {
745         Map<Integer, Collection<Candidate>> candidatesForNetworkId = new ArrayMap<>();
746         for (Candidate candidate : mCandidates.values()) {
747             Collection<Candidate> cc = candidatesForNetworkId.get(candidate.getNetworkConfigId());
748             if (cc == null) {
749                 cc = new ArrayList<>(2); // Guess 2 bssids per network
750                 candidatesForNetworkId.put(candidate.getNetworkConfigId(), cc);
751             }
752             cc.add(candidate);
753         }
754         return candidatesForNetworkId.values();
755     }
756 
757     /**
758      * Return a copy of the Candidates.
759      */
getCandidates()760     public List<Candidate> getCandidates() {
761         return mCandidates.entrySet().stream().map(entry -> entry.getValue())
762                 .collect(Collectors.toList());
763     }
764 
765     /**
766      * Make a choice from among the candidates, using the provided scorer.
767      *
768      * @return the chosen scored candidate, or ScoredCandidate.NONE.
769      */
choose(@onNull CandidateScorer candidateScorer)770     public @NonNull ScoredCandidate choose(@NonNull CandidateScorer candidateScorer) {
771         Preconditions.checkNotNull(candidateScorer);
772         Collection<Candidate> candidates = new ArrayList<>(mCandidates.values());
773         ScoredCandidate choice = candidateScorer.scoreCandidates(candidates);
774         return choice == null ? ScoredCandidate.NONE : choice;
775     }
776 
777     /**
778      * After a failure indication is returned, this may be used to get details.
779      */
getLastFault()780     public RuntimeException getLastFault() {
781         return mLastFault;
782     }
783 
784     /**
785      * Returns the number of faults we have seen
786      */
getFaultCount()787     public int getFaultCount() {
788         return mFaultCount;
789     }
790 
791     /**
792      * Clears any recorded faults
793      */
clearFaults()794     public void clearFaults() {
795         mLastFault = null;
796         mFaultCount = 0;
797     }
798 
799     /**
800      * Controls whether to immediately raise an exception on a failure
801      */
setPicky(boolean picky)802     public WifiCandidates setPicky(boolean picky) {
803         mPicky = picky;
804         return this;
805     }
806 
807     /**
808      * Records details about a failure
809      *
810      * This captures a stack trace, so don't bother to construct a string message, just
811      * supply any culprits (convertible to strings) that might aid diagnosis.
812      *
813      * @return false
814      * @throws RuntimeException (if in picky mode)
815      */
failure(Object... culprits)816     private boolean failure(Object... culprits) {
817         StringJoiner joiner = new StringJoiner(",");
818         for (Object c : culprits) {
819             joiner.add("" + c);
820         }
821         return failWithException(new IllegalArgumentException(joiner.toString()));
822     }
823 
824     /**
825      * As above, if we already have an exception.
826      */
failWithException(RuntimeException e)827     private boolean failWithException(RuntimeException e) {
828         mLastFault = e;
829         mFaultCount++;
830         if (mPicky) {
831             throw e;
832         }
833         return false;
834     }
835 
836     private boolean mPicky = false;
837     private RuntimeException mLastFault = null;
838     private int mFaultCount = 0;
839 }
840