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